JEP 387: Elastic Metaspace

JEP 387: Elastic Metaspace

Summary

사용하지 않은 HotSpot 클래스 메타데이터(즉, 메타 스페이스 ) 메모리를 운영 체제에 보다 신속하게 반환하고, 메타스페이스 공간을 줄이고, 메타스페이스 코드를 단순화하여 유지 관리 비용을 줄입니다.

Non-Goals

  • 압축된 클래스 포인터 인코딩이 작동하는 방식이나 압축된 클래스 공간이 존재한다는 사실을 변경하는 것이 목표가 아닙니다.
  • 메타 공간 할당자의 사용을 HotSpot의 다른 영역으로 확장하는 것이 목표는 아니지만 향후 향상될 수 있습니다.

Motivation

JEP 122: Remove the Permanent Generation 에서 시작된 이후로 메타 공간은 높은 오프 힙 메모리 사용량으로 악명이 높아졌습니다.
대부분의 일반 응용 프로그램에는 문제가 없지만 잘못된 방법으로 메타 공간 할당자를 간지럽게 하여 과도한 메모리 낭비를 유발하기 쉽습니다.
불행히도 이러한 유형의 병리학적 사례는 드문 일이 아닙니다.

메타스페이스 메모리는 클래스 로더별로 관리 됩니다.
경기장에는 로더가 저렴한 포인터 범프를 통해 할당하는 하나 이상의 청크 가 포함됩니다.
메타 공간 청크는 할당 작업을 효율적으로 유지하기 위해 거칠게 세분화됩니다.
그러나 이로 인해 많은 작은 클래스 로더를 사용하는 응용 프로그램이 비합리적으로 높은 메타 공간 사용을 겪을 수 있습니다.

클래스 로더가 회수되면 해당 메타스페이스 영역의 청크는 나중에 재사용할 수 있도록 여유 목록에 배치됩니다.
그러나 이러한 재사용은 오랫동안 발생하지 않을 수도 있고 절대 발생하지 않을 수도 있습니다.
따라서 클래스 로드 및 언로딩 활동이 많은 애플리케이션은 메타스페이스 여유 목록에 사용되지 않은 공간을 많이 축적할 수 있습니다.
해당 공간은 조각화되지 않은 경우 운영 체제에 반환되어 다른 용도로 사용할 수 있지만 그렇지 않은 경우가 많습니다.

Description

기존 메타스페이스 메모리 할당자를 버디 기반 할당 방식 으로 교체할 것을 제안합니다.
이것은 예를 들어 Linux 커널에서 성공적으로 사용된 오래되고 입증된 알고리즘입니다.
이 방식을 사용하면 메타스페이스 메모리를 더 작은 청크로 할당하는 것이 실용적이므로 클래스 로더 오버헤드가 줄어듭니다.
또한 단편화를 줄여 사용하지 않는 메타 공간 메모리를 운영 체제로 반환하여 탄력성을 향상시킬 수 있습니다.

또한 요청 시 운영 체제에서 경기장으로 메모리를 느리게 커밋합니다.
이렇게 하면 큰 경기장으로 시작하지만 즉시 사용하지 않거나 완전히 사용하지 않을 수 있는 로더(예: 부트 클래스 로더)의 공간이 줄어듭니다.

마지막으로, 버디 할당이 제공하는 탄력성을 최대한 활용하기 위해 메타스페이스 메모리를 서로 독립적으로 커밋 및 커밋되지 않을 수 있는 균일한 크기의 알갱이 로 정렬합니다.
이러한 그래뉼의 크기는 가상 메모리 조각화를 제어하는 간단한 방법을 제공하는 새로운 명령줄 옵션으로 제어할 수 있습니다.

새로운 알고리즘을 자세히 설명하는 문서는 여기 에서 찾을 수 있습니다. 작동하는 프로토타입 은 JDK 샌드박스 저장소에 분기로 존재합니다.

Alternatives

메타 공간을 현대화하는 대신 이를 제거하고 C 힙에서 직접 클래스 메타데이터를 할당할 수 있습니다.
이러한 변경의 이점은 코드 복잡성을 줄이는 것입니다. 그러나 C-힙 할당자를 사용하면 다음과 같은 단점이 있습니다.

  • 아레나 기반 할당자로서 메타스페이스는 클래스 메타데이터 개체가 대량 해제된다는 사실을 이용합니다.
    C-힙 할당자는 그런 사치가 없기 때문에 각 개체를 개별적으로 추적하고 해제해야 합니다. 그러면 런타임 오버헤드가 증가하고 개체를 추적하는 방법에 따라 코드 복잡성 및/또는 메모리 사용량이 증가합니다.

  • Metaspace는 포인터 범프 할당을 사용하여 매우 엄격한 메모리 패킹을 달성합니다. C-힙 할당자는 일반적으로 할당당 더 많은 오버헤드를 발생시킵니다.

  • C-힙 할당자를 사용하면 오늘날처럼 압축된 클래스 공간을 구현할 수 없으며 압축된 클래스 포인터에 대해 다른 솔루션을 찾아야 합니다.

  • C 할당자에 너무 많이 의존하면 위험이 따릅니다. C-힙 할당자는 높은 조각화 및 낮은 탄력성과 같은 고유한 문제 세트를 가질 수 있습니다.
    이러한 문제는 우리가 통제할 수 없기 때문에 문제를 해결하려면 운영 체제 공급업체와의 협력이 필요하며, 이는 시간 집약적일 수 있고 코드 복잡성 감소의 이점을 쉽게 무효화할 수 있습니다.

그럼에도 불구하고 우리 는 메타데이터 할당을 C 힙에 다시 연결한 프로토타입을 테스트 했습니다.
우리는 이 malloc기반 프로토타입을 위에서 설명한 버디 기반 프로토타입과 비교했으며 무거운 클래스 로딩 및 언로딩을 포함하는 마이크로 벤치마크를 실행했습니다.
C-힙 할당과 함께 작동하지 않기 때문에 이 테스트를 위해 압축된 클래스 공간을 껐습니다.

mallocglibc 2.23이 설치된 데비안 시스템에서 우리는 기반 프로토타입 에서 다음과 같은 문제를 관찰했습니다 .

  • 로드된 클래스의 수와 크기에 따라 성능이 8-12% 감소했습니다.
  • 메모리 사용량(프로세스 RSS) 은 클래스 언로딩 전 클래스 로드 피크에 대해 15-18% 증가했습니다 .
  • 메모리 사용량은 사용량 급증에서 전혀 복구되지 않았습니다. 즉, 메타 공간이 완전히 비탄력적이었습니다. 이로 인해 최대 153% 의 메모리 사용량 차이가 발생했습니다 .

이러한 관찰은 압축된 클래스 공간을 끌 때 발생하는 메모리 페널티를 숨깁니다. 이를 고려하면 malloc기반 변형에 대한 비교가 훨씬 더 불리할 수 있습니다.

Risks and Assumptions

Virtual-memory fragmentation

모든 운영 체제는 가상 메모리 범위를 어떤 방식으로든 관리합니다.
예를 들어 Linux 커널은 레드-블랙 트리를 사용합니다.
메모리 커밋을 취소하면 이러한 범위가 조각화되고 그 수가 증가할 수 있습니다.
이것은 특정 메모리 작업의 성능에 영향을 줄 수 있습니다.
OS에 따라 VM 프로세스에서 최대 메모리 매핑 수에 대한 시스템 제한이 발생할 수도 있습니다.

실제로 buddy allocator의 조각 모음 기능은 매우 우수하므로 메모리 매핑 수가 매우 약간 증가하는 것을 관찰했습니다.
매핑 수의 증가가 문제인 경우 그래뉼 크기를 늘려서 커밋 해제를 더 거칠게 만듭니다.
이렇게 하면 커밋되지 않은 기회를 일부 잃어버리면서 가상 메모리 매핑의 수를 줄일 수 있습니다.

Uncommit speed

OS가 페이지 테이블을 구현하는 방법과 이전에 범위가 얼마나 조밀하게 채워졌는지에 따라 큰 범위의 메모리 커밋을 해제하는 속도가 느릴 수 있습니다.
메타 공간 회수는 가비지 수집 일시 중지 중에 발생할 수 있으므로 문제가 될 수 있습니다.

우리는 지금까지 이 문제를 관찰하지 않았지만 커밋 해제 시간이 문제가 되면 커밋 해제 작업을 별도의 스레드로 오프로드하여 GC 일시 중지와 독립적으로 수행할 수 있습니다.

Reclamation policy

가상 메모리 조각화 또는 커밋 해제 속도와 관련된 잠재적인 문제를 처리하기 위해 메타 공간 회수 동작을 제어하는 새로운 프로덕션 명령줄 옵션을 추가합니다.

-XX:MetaspaceReclaimPolicy=(balanced|aggressive|none)

  • balanced: 대부분의 애플리케이션은 메타스페이스 메모리 공간이 개선되는 반면 메모리 회수의 부정적인 영향은 미미합니다. 이 모드는 기본값이며 이전 버전과의 호환성을 목표로 합니다.
  • aggressive: 가상 메모리 조각화 증가를 희생하여 증가된 메모리 회수율을 제공합니다.
  • none: 메모리 회수를 모두 비활성화합니다.

Maximum size of metadata

단일 메타스페이스 개체는 루트 청크 크기 보다 클 수 없습니다.
이 크기는 버디 할당자가 관리하는 가장 큰 청크 크기입니다.
루트 청크 크기는 현재 4MB로 설정되어 있으며 이는 메타 공간에 할당하려는 것보다 훨씬 큽니다.

참조