Java 는 프로그램 코드에서 메모리를 명시적으로 지정하여 해제하지 않는다. 대신, Garbage Collector 가 이를 대신 수행해준다.
'weak generational hypothesis' 가설을 바탕으로 HotSpot VM에서는 크게 2개로 물리적 공간 (Young 영역 / Old 영역)으로 나누어 GC를 수행한다.
'weak generational hypothesis' 가설
- 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
- Young 영역 : 새롭게 생성한 객체. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 많은 객체가 young 영역에 생성되었다가 사라짐
=> Minor GC - Old 영역 : 접근 불가능한 상태로 되지 않아 Young 영역에서 살아님은 객체가 복사되는 장소. Young 영역보다 크기가 크기 때문에 GC는 적게 발생한다.
=> Major GC 혹은 Full GC - Perm 영역 : 메소드 영역이라고도 하며, 객체나 인턴된 문자열 정보(Java 7 미만 만 해당)를 저장하는 곳으로 이름따라 Old 영역에서 살아남은 객체가 영원히 존재할 수 있는 영역이 아니며 GC가 발생할 수 있다.
=> Major GC의 횟수에 포함 - Old 영역의 Card Table : Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보 그 정보를 가지고 있다. 하여 Young 영역의 GC를 실행할 때, Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우 GC 대상으로 식별한다.
Young 영역은 최초로 객체가 만들어지는 Eden 영역과 Old 영역으로 이동하기 전의 저장소인 2개의 Survivor 영역으로 구성되어 있다.
- 새로 생성한 대부분의 객체는 Eden 영역에 위치한다.
- Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다.
- Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.
- 하나의 Survivor 영역이 가득 차게 되면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득 찬 Survivor 영역은 아무 데이터도 없는 상태로 된다.
- 이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게 된다.
따라서, Survivor 영역 중 하나는 반드시 비어 있는 상태로 남아 있다. 만약 아니라면 정상적인 상황이 아닌 것.
이처럼 보통은 Eden 영역에서 객체가 처음 만들어지고, Survivor 영역을 오가다가, 끝까지 남아 있는 객체는 Old 영역으로 이동하지만, Eden 영역에서 만들어지다가 크기가 커져서 Old 영역으로 바로 넘어가는 객체도 있긴 하다.
Old GC 종류
- Serial GC
단일 스레드로 처리. CPU 코어 개수가 적을 때 적합하다.
Old 영역에서 살아있는 객체를 mark 라고, 힙의 시작 점부터 확인하여 살아있는 것만 남기고(sweep), 마지막으로 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나누는(Compaction) mark-sweep-compact 알고리즘을 사용 - Parallel GC
코어의 개수가 많을 때 유리하다
Serial GC와 같은 방식으로 단일 스레드가 아닌 멀티 스레드 환경으로 처리한다. - Parallel Old GC(Parallel Compacting GC)
Serial GC 와는 Old 영역을 처리하는 Sweep 단계에서 다르다. 앞서 GC 수행한 영역에 대해서 별도로 살아있는 객체를 식별하는 (Summary) Mark-Summary-Compaction 단계를 거치며 처리한다. - Concurrent Mark & Sweep GC(이하 CMS)
초기 Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝낸다. 따라서, 멈추는 시간은 매우 짧다. 그리고 Concurrent Mark 단계에서는 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다. 이 단계의 특징은 다른 스레드가 실행 중인 상태에서 동시에 진행된다는 것이다.
그 다음 Remark 단계에서는 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다. 마지막으로 Concurrent Sweep 단계에서는 쓰레기를 정리하는 작업을 실행한다. 이 작업도 다른 스레드가 실행되고 있는 상황에서 진행한다.
이러한 단계로 진행되는 GC 방식이기 때문에 stop-the-world 시간이 매우 짧다. 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 부른다.
그런데 CMS GC는 stop-the-world 시간이 짧다는 장점에 반해 다음과 같은 단점이 존재한다.
1. 다른 GC 방식보다 메모리와 CPU를 더 많이 사용한다.
2. Compaction 단계가 기본적으로 제공되지 않는다.
따라서, CMS GC를 사용할 때에는 신중히 검토한 후에 사용해야 한다. 그리고 조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 stop-the-world 시간보다 stop-the-world 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 한다. - G1(Garbage First) GC
juns-lee.tistory.com/43
Old 영역으로 넘어가는 객체의 수 최소화하기
Old 영역의 GC는 New 영역의 GC에 비하여 상대적으로 시간이 오래 소요되기 때문에 Old 영역으로 이동하는 객체의 수를 줄이면 Full GC가 발생하는 빈도를 많이 줄일 수 있다. New 영역의 크기(-XX:NewSize)를 잘 조절함으로써 큰 효과를 볼 수 있다.
Full GC 시간 줄이기
Full GC의 실행 시간은 상대적으로 Minor GC에 비하여 길다. 그래서 Full GC 실행에 시간이 오래 소요되면(1초 이상) 연계된 여러 부분에서 타임아웃이 발생할 수 있다. 그렇다고 Full GC 실행 시간을 줄이기 위해서 Old 영역의 크기를 줄이면 자칫 OutOfMemoryError가 발생하거나 Full GC 횟수가 늘어난다. 반대로 Old 영역의 크기를 늘리면 Full GC 횟수는 줄어들지만 실행 시간이 늘어난다. Old 영역의 크기를 적절하게 '잘' 설정해야 한다.
GC의 성능을 결정하는 옵션
1. JVM 옵션
구분 | 옵션 | 설명 |
힙(heap) 영역 크기 | -Xms | JVM 시작 시 힙 영역 크기 |
-Xmx | 최대 힙 영역 크기 | |
New 영역의 크기 | -XX:NewRatio | New영역과 Old 영역의 비율 🌟 GC성능 좌우 |
-XX:NewSize | New영역의 크기 | |
-XX:SurvivorRatio | Eden 영역과 Survivor 영역의 비율 |
2. GC 방식
구분 | 옵션 | 비고 |
Serial GC | -XX:+UseSerialGC | |
Parallel GC | -XX:+UseParallelGC -XX:ParallelGCThreads=value |
|
Parallel Compacting GC | -XX:+UseParallelOldGC | |
CMS GC | -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=value -XX:+UseCMSInitiatingOccupancyOnly |
CMS 가비지 컬렉터는 자바 9버전에 Deprecated 선언되었다. 그리고 이번 자바 14버전에서 삭제됐다. |
G1 | -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC |
JDK 6에서는 두 옵션을 반드시 같이 사용해야 함 |
Parallel GC 사용 시 옵션 예시 ( +GC 모니터링 옵션 참조 )
-server // 동작모드
-Xms1536m
-Xmx1536m
-XX:PermSize=256m
-XX:MaxPermSize=512m
-verbosegc // GC 모니터링
-Xloggc:/.../tomcat/logs/gc_logs/gclog.log.`date +%Y%m%d%H%M%S` // GC로그를 남기는 것은 특별히 Java 애플리케이션 수행 성능에 영향을 미치지 않는다.
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-XX:+UseGCLogFileRotation
-XX:GCLogFileSize=10M
-XX:NumberOfGCLogFiles=5
-XX:SurvivorRatio=4 // New 영역:Old 영역 = 1:4
-XX:NewSize=512m
-XX:MaxNewSize=512m
-XX:ParallelGCThreads=2
-XX:+HeapDumpOnOutOfMemoryError // OutOfMemoryError로 인한 JVM에 종료시 Heap Dump를 생성해준다.
-XX:HeapDumpPath=/usr/tomcat/heapdumps
GC 처리 속도와 GC 발생 빈도에 때라 GC 튜닝 작업을 진행할지 결정한다.
다음의 조건에 모두 부합한다면 GC 튜닝이 필요 없다.
- Minor GC의 처리시간이 빠르다(50ms내외).
- Minor GC 주기가 빈번하지 않다(10초내외).
- Full GC의 처리시간이 빠르다(보통1초이내).
- Full GC 주기가 빈번하지 않다(10분에 1회).
참고
d2.naver.com/helloworld/1329
d2.naver.com/helloworld/184615
'JAVA' 카테고리의 다른 글
[자바] 스레드풀 튜닝 및 Executor 고급 활용 (0) | 2021.01.05 |
---|---|
JVM (0) | 2021.01.05 |
[JAVA] try with resources (0) | 2020.11.06 |
jar, class 파일 확인하기 (0) | 2019.12.02 |
make DI Framework by reflection (0) | 2019.09.29 |