GC 튜닝

: GC를 튜닝하고자 마음 먹었다면 할당, 중단 민감도, 처리율 추이, 객체 수명을 면밀히 관찰해야한다.

GC 힙 크기 조정 플래그

플래그 설명
-Xms<size> 힙 메모리의 최소 크기 설정
-Xmx<size> 힙 메모리의 최대 크기 설정
-XX:MaxPermSize=<size> 펌젠 메모리의 최대 크기 설정(Java 7 이전)
-XX:MaxMetaspaceSize=<size> 메타스페이스 메모리의 최대 크기를 설정(Java 8 이후)
  • Java7 이전에만 적용되던 펌젠은 Java8 이후로 메타스페이스로 교체되었다.
  • 튜닝 시 GC플래그는 한번에 하나씩만 추가하고, 각 플래그가 무슨 작용을 하는지 숙지해야하며 부수 효과를 일으키는 플래그 조합도 있음에 명시하고 사용하여야 한다.
  • 성능 문제를 일으키는 원인이 GC인지 아닌지를 판단하기 위해서는 vmstat 같은 고수준의 머신 지표를 체크한 뒤,
    • CPU 사용률이 100%에 가까운지?
    • 대부분의 시간(90%이상)이 user space에서 소비되는지?
    • GC 로그가 쌓이고 있다면 현재 GC가 실행중이라는 증거!
    위 항목들을 체크하자. 위 세가지 조건이 다 맞다면 GC가 성능 이슈를 일으키고 있을 가능성이 크다.


할당

  • 할당률 분석은 GC를 튜닝하면 성능이 개선될지 여부를 판단하는 데 반드시 필요한 과정이다.
  • Young generation 수집 이벤트 데이터를 활용하면 할당된 데이터양, 단위 수집 시간을 계산할 수 있고, 일정 시간 동안의 평균 할당률을 산출할 수 있다.
  • 1GB/s 이상의 할당률이 일정시간 지속한다면 GC 튜닝만으로는 해결할 수 없는 성능 문제가 발생한 경우일 확률이 크므로, 애플리케이션 핵심부의 할당 로직을 제거하는 리팩토링을 수행해 메모리 효율을 개선해야 한다.

할당과 관련된 주요 관찰 지점

  1. 굳이 없어도 그만인 사소한 객체 할당
    • 단순히 불필요한 객체를 제거한다.
    • 예: 로그 디버깅 메시지, JSON (un-)serialization 자동 생성코드, ORM 코드
  2. 박싱 비용
    • 불필요한 박싱을 언박싱한다.
  3. 도메인 객체
    • 다음 타입의 도메인 객체는 메모리를 많이 먹을 수 있으므로 참고하자.
    • char[]: 스트링을 구성하는 캐릭터
    • byte[]: 바이너리 데이터
    • double[]: 계산 데이터
    • Map Entry
    • Object[]
    • 내부 자료구조(methodOop, klassOop)
  4. 엄청나게 많은 non-JDK 프레임워크 객체

TLAB(Thread Local Allocation Buffer) 관련

  • TLAB는 스레드 당 크기가 동적으로 조정되며, 일반 객체는 남은 TLAB 공간에 할당된다. 만약 여유 공간이 없다면 스레드는 VM에게 새로운 TLAB를 달라고 요청한다.
  • 객체의 크기가 너무 커서 빈 TLAB에 들어가지 않으면 VM은 Eden에 직접 객체 할당을 시도하고, 이마저도 실패하면 영 GC를 수행한다. 만일 여기서도 실패하면 Tenured 영역에 객체를 직접 할당한다.
    • 덩치가 큰 배열(byte[], char[])가 테뉴어드에 곧바로 할당된다면 Weak generation hypothesis 이론에 의해 GC 성능이 급격히 안좋아질 수 있다.
  • 관련 튜닝 플래그는 다음과 같다.
    옵션 설명
    -XX:PretenureSizeThreshold=<n> 여기서 지정한 크기를 넘어가는 객체는 Young Genreation을 거치지 않고 Old Generation으로 바로 들어간다.
    -XX:MinTLABSize=<n> TLAB의 최소 크기를 설정한다
    -XX:MaxTenuringThreshold=<n> 테뉴어드 영역으로 승격되기 전까지 객체가 통과해야할 GC 횟수다.
    디폴트는 4회이고 1~15사이의 값을 가질 수 있다.
    한계치가 높을수록 장수한 객체가 더 많이 복사된다.
    한계치가 너무 낮으면 단명 객체가 승격되어 Tenured Memory pressure를 가중시킨다. 이는 full GC를 자주 발생시켜 성능을 저하시킨다.
  • 할당이 자주 발생하면 그만큼 Young GC의 발생 주기는 짧아지고 이렇게 짧은 시간 내에 Young GC가 자주 발생하면 Tenured 영역으로 잘못 승격되는 객체가 많아지므로 할당률은 GC의 성능에 큰 영향을 준다.

중단 시간

  • 중단 시간에 대한 정량적인 가치 판단의 기준은 존재하지 않는다. 다만 중단 시간 튜닝 시 참고가 될 만한 휴리스틱이 존재하는데, 다음의 표에서 나타내는 허용 중단시간 기준과 애플리케이션 힙 크기를 연관지어 적합한 초기 GC를 선택하길 바란다.
> 1sec 1sec - 100ms < 100ms -
Parallel Parallel CMS < 2GB
Parallel Parallel/G1 CMS < 4GB
Parallel Parallel/G1 CMS < 10GB
Parallel/G1 Parallel/G1 CMS < 20GB
Parallel/G1 G1 CMS > 20GB

 

CMS를 사용할 경우, 중단 시간을 튜닝하려고 하기 전에 할당률부터 줄이는 것이 좋다. 할당률이 낮아지면 CMS에 가해지는 memory pressure 또한 낮아지면 GC 사이클이 스레드 할당 속도를 따라가기 쉽다.
또한 이는 CMF 발생확률을 감소시켜, 중단시간에 민감한 애플리케이션에서 일거양득의 효과를 준다.

 

수집기 스레드와 GC 루트

  • GC루트 탐색 시간은 다음의 요소에 영향을 받음에 주의하자.
    • 애플리케이션 스레드 갯수
    • 코드 캐시에 쌓인 Compiled code
    • Heap 크기
  • GC루트 탐색은 단일 스레드로 수행하므로, 이 단일 스레드의 탐색 시간이 전체 마킹 시간을 경정짓게 된다. 이는 객체 그래프가 복잡해질수록 더욱 심해지며, 그래프 내부에 객체 체인이 길게 늘어지면서 마킹 시간은 점점 더 길어진다.
  • 애플리케이션 스레드가 너무 많아도 스택 프레임을 더 많이 탐색해야하고 세이프포인트에 도달하는 시간도 길어지는 등 GC 시간에 많은 영향을 끼친다.
  • GC루트의 원천들에는 JNI 프레임, JIT Compiled code 도 존재한다.
  • 위 세가지 요소 중 Heap 영역, 애플리케이션 스레드의 스택 프레임은 비교적 병렬화가 잘 된다.

+ Recent posts