ㅇ 여러분이 운영하고 만드는 시스템이 GC를 적게 하도록 하려면 객체 생성을 줄이는 작업을 먼저
ㅇ String대신 StringBuilder나 StringBuffer를 사용하는 것을 생활화하는 것부터가 시작
ㅇ (Eden 영역에서 객체가 처음 만들어지고, Survivor 영역을 오가다가, 끝까지 남아 있는 객체는 Old 영역으로 이동한다. 간혹 Eden 영역에서 만들어지다가 크기가 커져서 Old 영역으로 바로 넘어가는 객체도 있긴 하다)
Old 영역의 크기를 적절하게 '잘' 설정해야 한다.
ㅇ 특히 -Xms 옵션과 -Xmx 옵션은 필수로 지정해야 하는 옵션이다. 그리고 NewRatio 옵션을 어떻게 설정하느냐에 따라서 GC 성능에 많은 차이가 발생한다.
힙(heap) 영역 크기 | -Xms | JVM 시작 시 힙 영역 크기 |
-Xmx | 최대 힙 영역 크기 |
ㅇ 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 |
|
G1 | -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC |
JDK 6에서는 두 옵션을 반드시 같이 사용해야 함 |
ㅇ GC 상황을 확인한 후에는, 결과를 분석하고 GC 튜닝 여부를 결정해야 한다. 분석한 결과를 확인했는데 GC 수행에 소요된 시간이 0.1~0.3초 밖에 안 된다면 굳이 GC 튜닝에 시간을 낭비할 필요는 없다. 하지만 GC 수행 시간이 1~3초, 심지어 10초가 넘는 상황이라면 GC 튜닝을 진행해야 한다.
ㅇ jstat -gcutil
GC가 수행되는 시간을 확인했을 때 결과가 다음의 조건에 모두 부합한다면 GC 튜닝이 필요 없다.
- Minor GC의처리시간이빠르다(50ms내외).
- Minor GC 주기가빈번하지않다(10초내외).
- Full GC의처리시간이빠르다(보통1초이내).
- Full GC 주기가빈번하지않다(10분에 1회).
위에서 괄호에 있는 값은 절댓값은 아니고 서비스의 상황에 따라 달라질 수 있는 값이다
(https://d2.naver.com/helloworld/6043 - GC 모니터링)
ㅇ GC 튜닝을 가장 빨리 진행하는 방법은 무엇일까? 성능 테스트로 결과를 비교하는 것이 가장 빠른 검토 결과를 얻을 수 있는 방법이다.
ㅇ 아직 필자가 어떤 운영 서버에도 적용하지 않았지만, 몇몇 서버에 G1 GC 옵션을 지정하여 성능 테스트를 진행한 적이 있다. G1 GC는 그 어떤 GC 방식 보다 빠르지만, 이 옵션을 적용하려면 JDK 7으로 업그레이드해야 하한다.
https://d2.naver.com/helloworld/37111
ㅇ GC에 대해서 알아보기 전에 알아야 할 용어가 있다. 바로 'stop-the-world'이다. stop-the-world란, GC을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다. stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다. ... 대개의 경우 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것이다.
ㅇ GC 방식은 JDK 7을 기준으로 5가지 방식이 있다.
- Serial GC
- Parallel GC
- Parallel Old GC(Parallel Compacting GC)
- Concurrent Mark & Sweep GC(이하 CMS)
- G1(Garbage First) GC
https://d2.naver.com/helloworld/1329
ㅇ 그래서 full gc를 피하기가 어렵다. (그런데 full gc 가 상대적으로는 다른 JVM에 비해 매우 빠르긴 하다.)
full gc가 시간이 짧다고 해도 성격 상 JVM의 모든 프로세싱을 중지시키는 STOP-THE-WORLD 방식이기 때문에 문제가 없는 것은 아니다.(G1 GC에서는 young이든 old이든 copy하는 경우는 모두 STW 방식이다)
ㅇ 이들이 내세운 플래그는 바로 다음이다.
-XX:+UseG1GC -XX:+UnlockDiagnosticVMOptions -XX:+G1SummarizeConcMark -XX:InitiatingHeapOccupancyPercent=35 -XX:ConcGCThread=20
-XX:+PrintFlagsFinal -XX:+PrintReferenceGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy
이 옵션들 중 full gc를 회피하기 위해 알아둬야 할 G1 GC의 튜닝에관련된 핵심 정보는
- ConcGCThread갯수를 좀더 많이 사용하도록 옵션 설정
- InitiatingHeapOccupancyPercent옵션을 좀더 작게 설정하는 것이다.(기본값은 45이다.)
https://logonjava.blogspot.com/2015/08/java-g1-gc-full-gc.html
* Full GC
1) Initial Mark
2) Root Region Scan
3) Concurrent Mark
4) Remark
5) Clean Up
6) Copy
CMS와 같이 일부 구간은 STW된다. (Initial Mark, Remark, Copy)
https://sarc.io/index.php/java/255-g1-garbage-collector
STW (Stop-The-World)
Full GC가 발생하면 JVM은 어플리캐이션 실행을 멈추고 GC를 실행하는 쓰레드만 작동한다. 만약 웹서버에서 Full GC가 발생하면 서비스는 중단될 것이고 서비스가 중단 된 동안 각종 Time Out이 발생할 것이고 미뤄진 작업들이 누적되어 또 다른 Full GC를 발생시켜 장시간 장애가 발생 할 수 있다.
메모리 공간(heap)을 늘리면 Full GC를 피할 수 있을까? 메모리공간을 늘리면 Full GC의 첫 수행 시점은 늦출 수 있겠다. 하지만 STW의 시간은 heap 크기에 비례하기 때문에 메모리 공간이 클수록 더 많은 시간과 노력이 필요하다. 때문에 Heap을 많이 할당하는 것이 반드시 좋은 것만은 아니다.
어떤 GC알고리즘을 사용하더라도 Full GC와 STW는 발생한다. Full GC를 어떻게 제거할 것인가!? 하는 질문 보다 Full GC는 왜 발생하는가? 그리고 어떻게 피할것인가? 어떻게 최소화 할 것인가? 라는 질문에 초점을 맞춰 보자.
이러한 전제조건 아래에서 garbage를 처리하기 위해 HotSpot Vm)에서는 2개의 물리적 공간을 만들었다.
- Young Generation 영역 : 새롭게 생성한 객체의 대부분이 이곳에 위치. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 대부분의 객체가 Young 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질 때 Minor GC가 발생했다고 말한다.
- Old Generation 영역 : Minor GC이후에도 Young 역역에서 사라남은 객체가 여기로 복사 된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(혹은 Full GC)가 발생한다고 말한다.
Heap Area 구조 : https://www.journaldev.com/2856/java-jvm-memory-model-memory-management-in-java
'server' 카테고리의 다른 글
packet? frame? (calculate rtp packet based mtu) (0) | 2019.08.11 |
---|---|
class < jar < war < ear (0) | 2019.03.20 |
install ubuntu using virtualbox on mac (0) | 2019.03.09 |
L4 란 :: 로드밸런싱 / fail over (0) | 2018.12.02 |
socket.io (0) | 2018.11.24 |