2021.01.22 - [프로그래밍 노트/JAVA] - [JAVA] 가비지 컬렉터(GarabageCollector,GC)
GC(Garbage Collector) 란?
GC는 메모리 관리 기법 중 하나로 시스템에 있는 모든 객체의 수명을 정확히는 몰라도 런타임에 대신 객체를 추적하여 쓸모없는 객체를 알아서 제거해준다. 이렇게 자동 회수한 메모리는 깨끗이 비우고 재활용할 수 있게 된다.
- 즉, c나 c++ 처럼 개발자가 직접 메모리를 해제하는 것이 아닌 JVM 에서는 GC가 메모리를 자동 해제해 준다.
- 프로그래머가 저수준 세부를 일일이 신경쓰지 않는 대가로 저수준 제어권을 포기한다는 사상이 바로 자바 관리 방식의 핵심이다.
동적으로 할당했던 메모리 영역(Heap 영역)에서 필요 없게 된 메모리 객체(참조가 없는 객체)를 알아서 제거해준다.
++) GC는 다음 두 가진 기본 원칙을 준수해야 한다.
- 알고리즘은 반드시 모든 가비지를 수집해야 한다.
- 살아 있는 객체는 절대로 수집해선 안 된다.
++) GC의 단점
- GC가 실행되는 동안 다른 동작을 멈추기 때문에 오버헤드가 발생한다. G(STW - Stop-The-World)
- 언제 GC가 메모리를 해제하는 정확히 알 수 없음
GC 알고리즘 - Mark And Sweep
마크 앤 스위프
는 가비지 수집에 사용되는 기초적인 알고리즘이다. 사용되지 않는 객체를 식별(Mark)
하고 제거(Sweep)
하는 동작을 한다.
- GC Root로부터 살아 있는 객체를 찾는다.
- 살아있는 객체는 대부분 깊이-우선(detpth-first)방식으로 찾는다.
- 이렇게 찾은 객체마다 마크 비트를 세팅한다. (Mark)
- 마크 비트가 세팅되지 않은 객체를 찾아 메모리를 회수한다.(객체 삭제) (Sweep)
GC Root란?
Mark And Sweep
은 루트로 부터 해당 객체에 접근이 가능한지가 해제의 기준이 된다.
Runtime data areas 보면 GC의 Root는 Heap 메모리 영역을 참조하는 stack
, native method stack
, method area
영역이 된다.
GC 루트 종류
- 스택 프레임(stack frame)
- JNI (Native Method Stack)
- 레지스터(hoisted된 변수)
- (JVM 코드 캐시에서) 코드 루트
- 전역 객체
- 로드된 클래스의 메타데이터
약한 세대별 가설
약한 세대별 가설(Weak Generational Hypothesis)은 JVM메모리 관리의 이론적 근간을 형성한다.
- 거의 대부분의 객체는 아주 짧은 시간만 살아 있지만, 나머지 객체는 기대 수명이 훨씬 길다.
- 대부분의 객체는 금방 접근 불가 상태(Unreachable)이 된다.
- 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
- 즉, 객체는 대부분 일회성으로 생성되며 메모리에 오랫동안 남아있는 경우는 드물다.
핫스팟은 몇 가지 메커니즘을 응용하여 약한 세대별 가설을 십분 활용한다.
- 객체마다 ‘세대 카운트(generational count) - 객체가 지금까지 무사 통과한 가비지 수집 횟수’를 센다
- 큰 객체를 제외한 나머지 객체는 에덴(Eden)공간에 생성한다. 여기서 살아남은 객체는 다른 곳으로 옮긴다.
- 장수했다고 할 정도로 충분히 오래 살아남은 객체들은 별도의 메모리 영역(올드(old))에 보관한다.
가비지를 수집하는 힙은, 단명 객체를 쉽고 빠르게 수집할 수 있게 설계해야 하며, 장수 객체와 단명 객체를 완전히 떼어놓는게 가장 좋다
가비지 컬렉션 종류
Parallel GC
- JDK 8의 Default GC
- Serial GC의 알고리즘과 동일하지만 멀티 스레드로 작업이 수행되어 GC 수행 시간이 짧음
- Old 영역에서는 싱글 스레드로 수행되며, Youing 영역에서만 멀티 스레드로 수행
java -XX:+UseParallelGC -jar api.jar
G1 GC (Garbage First)
- JDK7 부터 정식 포함되어 사용 가능해졌고, JDK9 부터 디폴트 GC로 지정
- 기존 Heap 구조와는 다르게 Young/Old 영역을 명확하게 구분하지 않고 논리적 단위인 Region 이라는 개념을 도입해서 사용.
- Humonogous : Region 크기의 50%를 초과하는 큰 객체를 저장히기 위한 공간
- Available/Unused : 아직 사용되지 않은 Region
- 메모리가 많이 차 있는 Region을 우선적으로 GC. 전체가 아닌 Region을 나눠 탐색하고 Region별로 GC가 실행됨
- Heap크기가 클수록 잘 동작하며 Gabage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로 GC 빈도가 줄어듬
java -XX:+UseG1GC -jar api.jar
ZGC (Z Garbage Collector)
- JDK15에서 release
- G1의 Region 영역처럼, ZPage라는 영역으로 메모리를 나눠 관리하는 방식
- Zpage는 2MB 배수로 동적 운영됨
Heap 크기가 증가하여도 STW시간이 10초를 넘지 않음
- low-latency가 필요한 애플리케이션에 적당하며 STW 시간을 최소화하려는 특성상 CPU 사용량이 증가할 수 있음
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar api.java
GC 로깅 및 튜닝
GC 로깅
GC 로깅은 오버헤드가 거의 없는 것이나 다름없으니 주요 JVM 프로세스는 항상 로깅을 켜놓아야 한다.
필수 GC 플래그
-Xloggc:gc.log // GC 이벤트에 로깅할 파일을 지정
-XX:+PrintGCDetails // GC 이벤트 세부 정보를 로깅
-XX:+PrintTenuringDistribution // 툴링에 꼭 필요한, 부가적인 GC 이벤트 세부 정보를 추가
-XX:+PrintGCTimeStamps // GC 이벤트 발생 시간을 (VM 시작 이후 경과한 시간을 초 단위로) 출력
-XX:+PrintGCDateStamps // GC 이벤트 발생 시간을 (벽시계 시간 기준으로) 출력
GC 로그 순환 플래그 (데브옵스를 비롯한 운영팀과 협의해서 수립)
-XX:+UseGCLogFileRotation // 로그 순환 기능을 켠다.
-XX:+NumberOfGCLogFiles=<n> // 보관 가능한 최대 로그파일 개수를 설정
-XX:+GCLogFileSize=<size> // 순환 직전 각 파일의 최대 크기를 설정
GC 기본 튜닝
-Xms<size> // 힙 메모리의 최소 크기를 설정
-Xmx<size> // 힙 메모리의 최대 크기를 설정
-XX:MaxPermSize=<size> // 펌젠 메모리 최대 크기를 설정 (자바 7 이전)
-XX:MaxMetaspaceSize=<size> // 메타스페이스 메모리의 최대 크기를 설정 (자바 8 이후)
- 일반적으로 시스템 성능을 최대한 활용하기 위해 전체 물리 메모리의 50-75% 정도를 JVM 힙에 할당하는 것이 권장됨
- OS와 다른 프로세스가 안정적 동작을 하기 위해 약 4GB 정도는 남기는 것이 좋음
- 만약 16GB 머신이라면,
- 50% 할당(8GB) - 적은 힙 메모리로도 충분한 경우
- 75% 할당(12GB) - 메모리 집약적인 경우
- 너무 큰 힙은 GC에 부하를 주어 오히려 성능 저하를 유발하며, 반대로 너무 작은 힙은 자주 GC가 발생하여 애플리케이션 성능에 영향을 줌
참고)
'프로그래밍 노트 > JAVA' 카테고리의 다른 글
[JAVA] Collectors 클래스 정적 팩토리 메서드 (0) | 2021.09.26 |
---|---|
[JAVA] Stream Collectors - 2. 그룹화 (groupingBy) (1) | 2021.09.26 |
[JAVA] Stream Collectors - 1. 리듀싱(reducing) (0) | 2021.09.23 |
[JAVA]숫자형 스트림 (IntStream, DoubleStream ..) (0) | 2021.09.20 |
[JAVA]Stream 리듀싱 활용 (0) | 2021.09.20 |