>  기사  >  Java  >  Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

php是最好的语言
php是最好的语言원래의
2018-07-28 16:55:512267검색

Java 가비지 수집 시간을 90% 줄이는 방법은 무엇입니까? 누구나 Java의 GC에 익숙해야 합니다. GC 최적화가 수행되는 방법은 아래에서 자세히 설명합니다. JVM의 GC 메커니즘은 개발자가 메모리 관리의 세부 사항으로부터 보호하고 개발 효율성을 향상시킵니다. apache php mysql

저희는 얼마 전 Ali-HBase에서 이러한 공통적인 문제점을 극복하기 위해 준비한 바 있으며, 이를 위해 심층적인 분석과 포괄적인 혁신 작업을 수행하여 비교적 좋은 결과를 얻었습니다. Ant 위험 제어 시나리오를 예로 들면, HBase의 온라인 Young GC 시간은 Alibaba JDK 팀에서 제공하는 도구인 ZenGC와 결합하여 실험실 스트레스 테스트 환경에서 5ms에 더욱 도달했습니다. 이 기사에서는 주로 이 분야에 대한 우리의 과거 작업과 기술 아이디어 중 일부를 소개합니다.

배경 소개

JVM의 GC 메커니즘은 개발자가 메모리 관리의 세부 사항으로부터 보호하고 개발 효율성을 향상시킵니다. GC에 대해 말하면 많은 사람들의 첫 번째 반응은 JVM이 오랫동안 일시 중지되거나 FGC로 인해 프로세스가 중단되어 서비스할 수 없다는 것입니다. 그러나 HBase와 같은 빅데이터 스토리지 서비스의 경우 JVM으로 인해 발생하는 GC 문제는 상당히 복잡하고 어렵습니다. 세 가지 이유가 있습니다:

1. 메모리 크기가 엄청납니다. 대부분의 온라인 HBase 프로세스는 96G 대형 힙입니다. 올해의 새로운 모델은 이미 160G 이상의 일부 힙 구성을 출시했습니다. 개체 상태가 복잡합니다. HBase 서버는 내부적으로 수십 GB 규모에 달하는 많은 수의 읽기 및 쓰기 캐시를 유지 관리합니다. HBase는 테이블 형식으로 정렬된 서비스 데이터를 제공합니다. 이러한 데이터 구조는 1억 개가 넘는 개체와 참조를 생성합니다. 3. Young GC 빈도가 높습니다. 액세스 압력이 클수록 Young 영역의 메모리 소비가 빨라집니다. 일부 사용량이 많은 클러스터는 초당 1~2개의 Young GC에 도달할 수 있지만 Young 영역이 커지면 Young GC 일시 중지가 늘어나고 손상될 수 있습니다. 비즈니스.

Thinking

HBase는 스토리지 시스템으로서 쓰기 버퍼와 읽기 캐시로 많은 메모리를 사용합니다. 예를 들어 96G(4G young + 92G old)의 큰 힙에서는 쓰기 버퍼 + 읽기 캐시가 차지합니다. 메모리의 70% 이상(약 70G)인 경우 힙 자체의 메모리 수준은 85%로 제어되며 나머지 점유 메모리는 10G 이내입니다. 따라서 애플리케이션 수준에서 이 70G+ 메모리를 자체 관리할 수 있다면 JVM의 경우 100G의 큰 힙의 GC 압력은 10G의 작은 힙의 GC 압력과 동일하며 더 큰 문제에 직면하게 됩니다. 앞으로는 팽만감을 악화시키지 않을 것입니다. 이 솔루션에서는 온라인 Young GC 시간이 120ms에서 15ms로 최적화되었습니다.

  1. 처리량이 많은 데이터 집약적인 서비스 시스템에서는 수많은 임시 개체가 자주 생성되고 재활용됩니다. AliJDK 팀은 이러한 임시 개체의 할당 및 재활용을 어떻게 관리할 수 있을까요? 테넌트 기반 GC 알고리즘—ZenGC. 이 새로운 ZenGC 알고리즘을 기반으로 그룹의 HBase가 변환되었습니다. 실험실에서 측정한 Young GC 시간은 15ms에서 5ms로 단축되었습니다. 이는 예상치 못한 극단적인 효과입니다.

  2. 다음은 Ali-HBase 버전의 GC 최적화에 사용되는 핵심 기술을 하나씩 소개하겠습니다.

  3. 더 빠르고 더 경제적인 CCSMap

현재 HBase에서 사용하는 스토리지 모델은 LSMTree 모델입니다. 작성된 데이터는 임시로 메모리에 일정 크기로 저장된 후 디스크에 덤프되어 파일로 만들어집니다.

아래에서는 이를 쓰기 캐시라고 부르겠습니다. 쓰기 캐시는 쿼리 가능하므로 데이터를 메모리에서 정렬해야 합니다. 동시 읽기 및 쓰기의 효율성을 향상시키고 데이터 정렬 및 탐색 및 스캔 지원의 기본 요구 사항을 달성하기 위해 SkipList는 널리 사용되는 데이터 구조입니다.

JDK와 함께 제공되는 ConcurrentSkipListMap을 분석의 예로 사용합니다. 여기에는 다음과 같은 세 가지 문제가 있습니다.

Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

내부 개체가 많습니다. 요소가 저장될 때마다 평균 4개의 객체(인덱스+노드+키+값, 평균 레이어 높이 1)가 필요합니다. 새로 삽입된 객체는 Young 영역에 있고 오래된 객체는 Old 영역에 있습니다. 요소가 연속적으로 삽입되면 ParNew 알고리즘의 CardTable 마크이든 G1 알고리즘의 RSet 마크이든 내부 참조 관계가 자주 변경됩니다. 이전 영역 스캔을 트리거할 수 있습니다.

  1. 업체에서 작성한 KeyValue 요소가 일정한 길이가 아닙니다. 이전 영역으로 승격되면 대량의 메모리 조각이 생성될 수 있습니다.

  2. 문제 1은 Young Area GC의 객체 스캔 비용을 매우 높게 만들고 Young GC 동안 더 많은 객체가 승격됩니다. 문제 2는 Young GC 중에 스캔해야 하는 Old 영역이 확장되는 원인입니다. 문제 3은 메모리 조각화로 인한 FGC 가능성을 높입니다. 작성되는 요소가 작을수록 문제는 더욱 심각해집니다. 우리는 온라인 RegionServer 프로세스에 대한 통계를 만들었으며 활성 개체가 1억 2천만 개에 달하는 것으로 나타났습니다!

  3. 현재 Young GC의 가장 큰 적을 분석한 결과 쓰기 캐시의 할당, 액세스, 파괴 및 재활용이 모두 우리에 의해 관리되므로 JVM이 쓰기 캐시를 "볼 수 없다면" 과감한 아이디어가 나왔습니다. 쓰기 캐시의 라이프사이클을 우리가 직접 관리하고, GC 문제는 자연스럽게 해결될 것입니다.
  4. JVM을 "보이지 않게" 만든다고 하면 많은 사람들이 오프 힙 솔루션을 생각할 수도 있지만 이는 쓰기 캐시의 경우 그리 간단하지 않습니다. 왜냐하면 KeyValue가 힙 외부에 배치되더라도 문제 1과 1을 피할 수 없기 때문입니다. 질문 2. 그리고 1번과 2번은 Young GC의 가장 큰 문제이기도 합니다.

    질문은 이제 JVM 객체를 사용하지 않고 동시 액세스를 지원하는 순서화된 맵을 구축하는 방법으로 변환됩니다.

    물론 Map 작성 속도는 HBase의 쓰기 처리량과 밀접한 관련이 있기 때문에 성능 손실을 받아들일 수 없습니다.

    요구가 다시 강화됩니다: 객체를 사용하지 않고 성능 손실 없이 동시 액세스를 지원하는 순서화된 맵을 구축하는 방법.

    이 목표를 달성하기 위해 다음과 같은 데이터 구조를 설계했습니다.

    • JVM의 개체 메커니즘에 의존하는 대신 연속 메모리(힙 내부 또는 힙 외부)를 사용하여 내부 구조를 제어합니다

    • 이는 또한 잠금 없는 동시 쓰기 및 쿼리를 지원하는 SkipList이기도 합니다. 제어 포인터와 데이터는 연속 메모리 구조에 저장됩니다. 우리는 대용량 메모리 세그먼트(청크) 형태의 쓰기 캐시 메모리를 적용합니다. 각 청크에는 여러 노드가 포함되어 있으며 각 노드는 요소에 해당합니다. 새로 삽입된 요소는 항상 사용된 메모리의 끝에 배치됩니다. Node 내부의 복잡한 구조에는 Index/Next/Key/Value 등의 유지 관리 정보 및 데이터가 저장됩니다. 새로 삽입된 요소는 노드 구조에 복사되어야 합니다. HBase에서 쓰기 캐시 덤프가 발생하면 전체 CCSMap의 모든 청크가 재활용됩니다. 요소가 삭제되면 연결 목록에서 해당 요소를 논리적으로 "제거"할 뿐이며 실제로 메모리에서 요소를 복구하지는 않습니다(물론 실제 복구를 수행하는 방법이 있지만 HBase에 관한 한 필요하지 않습니다).

    • KeyValue 데이터를 삽입할 때 추가 복사본이 있지만 대부분의 경우 복사가 더 빨라집니다. CCSMap의 구조상 Map에 있는 요소의 Control Node와 KeyValue가 메모리에서 인접해 있기 때문에 CPU 캐시를 사용하는 것이 더 효율적이고 탐색 속도가 더 빠릅니다. SkipList의 경우 쓰기 속도는 실제로 탐색 속도에 의해 제한되며 실제 복사로 인한 오버헤드는 탐색 오버헤드보다 훨씬 적습니다. 테스트에 따르면 JDK와 함께 제공되는 ConcurrentSkipListMap과 비교하여 50Byte 길이 KV 테스트에서 읽기 및 쓰기 처리량이 20~30% 증가했습니다.
    • JVM 객체가 없기 때문에 각 JVM 객체는 최소 16바이트의 공간을 차지하고 저장 가능합니다(8바이트는 태그용으로 예약되어 있고 8바이트는 유형 포인터입니다). 50Byte 길이의 KeyValue를 예로 들면, JDK에 포함된 ConcurrentSkipListMap과 비교하여 CCSMap의 메모리 사용량이 40% 감소합니다.

    • CCSMap이 프로덕션에 출시된 후 실제 최적화 효과: Young GC가 120ms+에서 30ms로 감소했습니다.

    Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.최적화 전

    최적화 후

    CCSMap을 사용한 후 원래 1억 2천만 개의 생존 객체가 10개 이내로 감소했습니다. 수백만 수준으로 GC 압력이 크게 감소합니다. 컴팩트한 메모리 배열로 인해 쓰기 처리량도 30% 향상되었습니다.

    캐시: BucketCache

    HBase는 디스크의 데이터를 블록 형태로 정리합니다. 일반적인 HBase 블록 크기는 16K~64K입니다. HBase는 디스크 I/O를 줄이기 위해 BlockCache를 내부적으로 유지합니다. 쓰기 캐시와 마찬가지로 BlockCache는 GC 알고리즘 이론의 세대 가설을 따르지 않으며 본질적으로 GC 알고리즘에 비우호적입니다. 일시적이거나 영구적이지 않습니다.

    Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.블록 데이터 조각이 디스크에서 JVM 메모리로 로드됩니다. 수명 주기는 몇 분에서 몇 달까지입니다. 대부분의 블록은 이전 영역에 들어가고 Major GC 중에만 JVM에 의해 재활용됩니다. 그 문제는 주로 다음과 같이 반영됩니다.

    HBase 블록의 크기가 고정되지 않고 상대적으로 크며 메모리가 쉽게 단편화됩니다. Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

    ParNew 알고리즘에서는 승격이 문제가 됩니다. 문제는 복사 비용에 반영되는 것이 아니라 크기가 크고 HBase Block을 저장할 적절한 공간을 찾는 데 드는 비용이 높기 때문입니다.

    읽기 캐시 최적화의 아이디어는 BlockCache로 반환되지 않는 메모리 조각을 JVM에 적용하는 것입니다. 블록이 메모리에 로드될 때 우리는 메모리를 고정 크기 세그먼트로 분할합니다. , 블록을 분할된 범위에 복사하고 사용된 것으로 표시합니다. 이 블록이 더 이상 필요하지 않으면 간격을 사용 가능한 것으로 표시하고 새 블록을 복원할 수 있습니다. 이것이 BucketCache입니다. BucketCache의 메모리 공간 할당 및 재활용과 관련하여(이 영역의 설계 및 개발은 수년 전에 완료되었습니다)

    BucketCache

    오프힙 메모리를 기반으로 하는 많은 RPC 프레임워크는 오프힙 메모리 자체의 할당 및 재활용도 관리합니다. 일반적으로 메모리는 명시적 해제를 통해 회수됩니다. 하지만 HBase에는 몇 가지 어려움이 있습니다. 우리는 블록 객체를 자체 관리해야 하는 메모리 세그먼트로 생각합니다. 블록은 여러 작업에서 참조될 수 있습니다. 블록 재활용 문제를 해결하는 가장 간단한 방법은 각 작업의 스택에 블록을 복사한 다음(복사된 블록은 일반적으로 이전 영역으로 승격되지 않음) 관리를 위한 JVM.

    실제로 우리는 이전에 이 방법을 사용해 왔습니다. 구현이 간단하고 JVM이 승인하며 안전하고 신뢰할 수 있는 방법입니다. 그러나 이는 GC 문제를 해결하기 위해 각 요청에 대한 복사 비용이 도입되는 손실 메모리 관리 방법입니다. 스택에 복사하려면 추가 CPU 복사 비용과 Young Area 메모리 할당 비용이 필요하므로 CPU와 버스가 점점 더 귀중해지는 오늘날에는 이 가격이 높아 보입니다.

    그래서 우리는 메모리 관리를 위해 참조 카운팅을 사용하게 되었습니다. HBase에서 직면하게 되는 주요 어려움은 다음과 같습니다.

    1. HBase 내부에는 동일한 블록을 참조하는 여러 작업이 있습니다.

    2. 동일한 작업 변수 내에 여러 작업이 있을 수 있습니다. 동일한 블록을 참조하십시오. 참조는 스택의 임시 변수이거나 힙의 개체 필드일 수 있습니다.

    3. Block의 처리 논리는 상대적으로 복잡합니다. Block은 매개변수, 반환 값 및 필드 할당의 형태로 여러 함수와 개체 간에 전달됩니다.

    4. 블록은 당사에서 관리할 수도 있고 관리하지 않을 수도 있습니다(일부 블록은 수동으로 해제해야 하지만 일부 블록은 그렇지 않음).

    5. Block은 Block의 하위 유형으로 변환될 수 있습니다.

    이러한 점을 종합하면 올바른 코드를 작성하는 것이 어려운 일입니다. 그런데 C++에서는 스마트 포인터를 사용하여 객체 수명주기를 관리하는 것이 당연합니다. Java에서는 왜 어려운가요?

    사용자 코드 수준에서 Java의 변수 할당은 참조 할당 동작만 생성하는 반면 C++의 변수 할당은 객체의 생성자와 소멸자를 사용하여 많은 작업을 수행할 수 있으며 스마트 포인터는 이를 기반으로 구현됩니다(물론 C++ 생성자와 소멸자를 부적절하게 사용하면 많은 문제가 발생할 수 있으며 각각 장단점이 있으므로 여기서는 논의하지 않습니다.)

    그래서 우리는 C++의 스마트 포인터를 참조하여 블록 참조 관리 및 재활용 프레임워크인 ShrableHolder를 설계했습니다. 그렇지 않으면 코딩에 어려움이 있습니다. 여기에는 다음과 같은 패러다임이 있습니다.

    1. ShrableHolder는 참조 계산 개체와 참조 계산되지 않는 개체를 관리할 수 있습니다.

    2. ShrableHolder는 재할당될 때 이전 개체를 해제합니다. 관리 객체인 경우 참조 횟수가 1씩 감소하고, 그렇지 않은 경우에는 변경 사항이 없습니다.

    3. ShrableHolder는 작업이 끝나거나 코드 세그먼트가 끝나면 재설정을 호출해야 합니다.

    4. ShrableHolder는 직접 할당할 수 없습니다. 콘텐츠를 전송하려면 ShrableHolder에서 제공하는 메소드를 호출해야 합니다.

    5. ShrableHolder는 직접 할당할 수 없기 때문에 수명주기 의미가 포함된 블록을 함수에 전달해야 할 때 ShrableHolder를 함수의 매개 변수로 사용할 수 없습니다.

    이 패러다임에 따라 작성된 코드는 원래 코드에 대한 논리적 변경 사항이 거의 없으며 다른 내용이 도입되는 경우도 없습니다. 여전히 다소 복잡해 보이지만 다행히도 이로 인해 영향을 받는 범위는 여전히 HBase에 허용되는 매우 로컬 하위 계층으로 제한됩니다. 안전을 유지하고 메모리 누수를 방지하기 위해 오랫동안 비활성 상태였던 참조를 감지하는 감지 메커니즘을 이 프레임워크에 추가했습니다. 일단 발견되면 강제로 삭제 표시됩니다.

    BucketCache를 적용한 후 BlockCache의 승격 오버헤드가 줄어들고 Young GC 시간이 줄어듭니다.

    Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

    Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

    (CCSMap+BucketCache 최적화 후 효과)

    ZenGC에 대한 간략한 이야기

    이후 두 가지 주요 최적화 후 Ant Risk Control 프로덕션 환경의 Young GC 시간이 15ms로 단축되었습니다. ParNew+CMS 알고리즘을 이 규모로 최적화하는 것은 이미 어렵기 때문에 ZenGC로 전환했습니다. ZenGC는 G1 알고리즘을 기반으로 심층적인 개선을 이루었으며 HBase와 ZenGC의 자체 관리 메모리 힙은 우수한 화학 반응을 만들어냈습니다.

    ZenGC는 G1 알고리즘을 기반으로 Alibaba JVM 팀이 최적화하고 대용량 힙(LargeHeap) 애플리케이션 시나리오를 지향하는 GC 알고리즘의 총칭입니다. 여기서는 주로 Multi-tenant GC를 소개합니다.

    다중 테넌트 GC에는 세 가지 핵심 논리 계층이 포함되어 있습니다. 1) JavaHeap에서는 개체 할당이 테넌트에 따라 격리되고 여러 테넌트가 서로 다른 힙 영역을 사용합니다. 2) 단순히 비용이 적게 드는 것이 아니라 더 적은 비용으로 테넌트 세분성에서 GC가 발생하도록 허용합니다. 전체 애플리케이션만 3) 상위 계층 애플리케이션이 비즈니스 요구에 따라 테넌트를 유연하게 매핑할 수 있도록 허용합니다.

    ZenGC는 메모리 영역을 여러 테넌트로 나누고 각 테넌트에서 독립적으로 GC를 트리거합니다. 이를 바탕으로 메모리를 일반 테넌트와 중간 수명 주기 테넌트로 구분합니다. 중간 수명 개체는 일시적이거나 영구적이지 않은 개체입니다. 위의 두 가지 주요 최적화로 인해 이제 힙의 수명 주기 개체 수와 메모리 사용량이 매우 적습니다. 그러나 중간 수명 주기 개체는 생성될 때 오래된 영역 개체에 의해 참조되며 각 젊은 GC는 여전히 젊은 GC에서 가장 시간이 많이 걸리는 부분인 RSet를 스캔해야 합니다.

    AJDK팀의 ObjectTrace 기능의 도움으로 중간 수명주기 개체의 "가장 큰" 부분을 찾아내고, 이러한 개체가 생성되면 이러한 개체를 중간 수명주기 테넌트의 이전 영역에 직접 할당합니다. RSet 표시를 방지합니다. 일반 테넌트는 일반적인 방법으로 메모리를 할당합니다.

    일반 테넌트의 GC 빈도는 매우 높으나 승격 대상이 적고 세대 간 참조가 적기 때문에 Young zone에서는 GC 시간이 잘 제어됩니다. 실험실 장면 시뮬레이션 환경에서는 Young GC를 5ms로 최적화했습니다.

    Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

    (ZenGC 최적화 효과, 유닛 문제, 여기 있습니다)

    Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

    Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.

    클라우드에서의 사용

    Ali-HBase는 현재 Alibaba Cloud에서 상용 서비스를 제공하고 있습니다. Alibaba Cloud에서 크게 개선된 원스톱 HBase 서비스를 사용해 보세요. 자체 구축된 HBase에 비해 클라우드 HBase 버전은 운영 및 유지 관리, 신뢰성, 성능, 안정성, 보안 및 비용 측면에서 많은 개선이 이루어졌습니다.

    관련 기사:

    Java 가비지 수집 오버헤드를 줄이기 위한 5가지 제안

    java 가비지 수집

    관련 동영상:

    가비지 수집 메커니즘 - Han Shunping 2016 최신 PHP 객체 지향 프로그래밍 비디오 튜토리얼

위 내용은 Java 가비지 수집 시간도 쉽게 줄일 수 있습니다. Ali-HBase의 GC를 예로 설명합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.