[JVM] 기본 GC 튜닝
4개의 GC 알고리즘에 공통적으로 적용되는 기본적인 튜닝 방법에 대해 알아보자
힙이 너무 작다면 너무 자주 GC가 일어날 것이고,
힙이 너무 크다면 중단은 줄어들지만 Full GC가 발생했을 때 중단 시간이 너무 길어진다.
힙 사이즈는 [물리적 RAM - (OS에서 사용하는 RAM)] 보다 작게!
OS는 스와핑, Paging을 통해 메모리를 가상화시켜서 관리한다.
예를들어 OS는 실제로 PC의 RAM은 8GB이지만 가상화를 통해 16GB로 보이도록 제공한다.
이때, 실제로 물리적인 RAM은 8GB이기 때문에, 나머지 8GB를 디스크에서 가져와서 사용한다.
이 과정은 사용자에게는 투명하지만 내부적으로 디스크에 있는 데이터에 접근할때마다 느려질 것이다.
힙 크기를 물리적인 RAM크기 보다 작게 지정하자. 일반적으로 보통 OS에서 프로파일을 위해서 적어도 1GB의 공간이 필요하기 때문에 RAM - 1GB의 크기로 힙사이즈를 지정하자.
힙 크기를 물리적인 RAM 보다 크게 지정할 경우, Full GC가 발생한다면 디스크에 있는 가상 RAM에 접근해서 Full GC 를 처리해야하기 때문에 엄청 느려진다.
디스크 가상화를 통한 메모리에 GC가 발생하여 디스크 접근이 발생할 수 있다는 것을 주의하자!
명시하지 않으면 운영체제와 JVM에 따라 Default 값이 사용된다.
초기 힙, 최대 힙 사이즈를 모두 지정할 경우 JVM은 GC가 너무 많이 발생한다면 최대 힙 사이즈가 될 때까지 힙을 지속적으로 늘린다.
JVM이 알아서 명시한 사이즈 내에서 적합한 힙 크기를 찾으려 시도한다.
큰 힙이 필요한 어플리케이션이 아니라면, 그냥 디폴트 값을 사용하고 힙 크기를 미세조정하는 시간에 다른 튜닝을 고려하자.
적절한 힙 사이즈를 찾는 공식은 없다. 어플리케이션이 돌아가는 것을 관찰하고 힙 사이즈를 조정해가면서 GC 가 일어나는 시간을 파악하고, 적절한 성능 목표치 내에서 타협해야 한다.
어플리케이션을 관찰하고 적절한 힙 사이즈가 측정되었다면,
-Xms4096m -Xmx4096m 처럼 힙 크기를 고정할 수 있다.
힙 크기를 고정하면 힙 크기 재조정 여부를 파악하는데 쓰이는 불필요한 작업들이 없어지기 때문에 GC 는 약간 더 효율적이 된다.
지정된 힙 크기 내에서 영/올드 제너레이션의 크기를 지정해야 한다.
영 제너레이션이 비교적 크다면 Minor GC가 덜 발생하고 더 작은 객체가 Old 제너레이션으로 간다. 하지만 비교적 올드 제너레이션이 더 작기 때문에 더 자주 Full GC가 발생한다. 이 균형을 설정하는 것이다.
영 제너레이션을 설정하는 플래그만 제공한다.
나머지 공간은 모두 올드 제너레이션이 차지한다.
-XX:NewRatio=N ( Default = 2)
올드 제너레이션과 영 제너레이션의 비율을 설정한다
-XX:NewSize=N
영 제너레이션의 초기 크기를 설정한다
-XX:MaxNewSize=N
영 제너레이션의 최대 크기를 설정한다
-XmnN
NewSize, MaxNewSize에 동일한 값 N을 설정한다
Default 크기
NewSize, MaxNewSize를 지정하지 않을 경우, 아래 공식에 따라 초기 영 제너레이션의 크기가 결정된다.
초기 영 제너레이션의 크기 = 초기 힙 크기 / ( 1 + NewRatio )
NewRatio값은 디폴트가 2기때문에, 영 제너레이션은 초기 힙 크기의 33%가 된다.
-XX:PermSize=N ~ -XX:MaxPermSize=N
-XX:MetaspaceSize=N ~-XX:MaxMetaspaceSize=N
힙 메모리는 영/올드 외에도 펌 제너레이션 영역이 있다.
JVM이 로드할 클래스들의 메타데이터를 저장하는 공간이다.
독립적인 힙처럼 동작한다.
Java 7에서는 PERM이였던 것이 Java 8에서는 메타스페이스로 불린다.
이곳도 튜닝될 필요가 있다!
메타 스페이스가 가득차면?
OOME :Out Of Memory Error 발생!
Perm 에 저장된 클래스 정보 데이터는 영구적이지 않다.
개발중인 어플리케이션 서버에서는 펌 영역이 가득차고 기존 클레스 메타 데이터가 폐기될 때 가끔 Full GC 가 발생한다.
힙 덤프를 통해 어떤 클래스 로더가 동작했는지 진단할 수 있다.
이를 통해 클로스로더에 누수가 있는지 알아낼 수 있다.
시리얼 컬렉터를 제외한 모든 GC 알고리즘은 여러 개의 스레드를 사용한다.
이 때 사용되는 스레드의 개수를 지정한다.
단, CMS 나 G1에서 사용하는 백그라운드 스레드의 수를 설정하지는 않는다.
스레드의 개수는 아래의 경우만 영향을 받는다
-XX:+UseParallelGC & Minor GC 일때
-XX:+UseParallelOldGC & Full GC 일때
-XX:+UseParNewGC & Minor GC
-XX:+UseG1GC & Minor GC
CMS의 stop-the-world 일때 ( Not Full GC)
G1의 stop-the-world 일때 ( Not Full GC)
머신 내의 CPU 개수를 기반으로 기본 스레드 개수가 지정된다
CPU 8개 이상을 갖는 머신에서의 기본 스레드 개수는 다음과 같다
ParallelGCThreads = 8 + ( (N-8) * 5 / 8)
-XX:-UseAdaptiveSizePolicy (Default : true)
주어진 힙 크기 내에서 좋은 성능을 내도록 JVM이 자동으로 영/올드 비율을 튜닝한다.
힙 안의 제너레이션의 크기는 JVM이 최적의 성능을 찾고자 시도하면서 실행하는 동안 달라질 수 있다.
힙 내에서 영 제너레이션과 올드 제너레이션의 비율을 자동으로 바꿔준다.
대게 GC로 인해 중단이 일어나는 동안 크기 조정이 일어난다.
플래그 이용해서 끄지 않더라도, 최대, 최소 크기를 갖게 설정할 경우 자동 크기 조정이 동작하지 못한다.
-XX:+PrintAdaptiveSizePolicy
JVM이 어플리케이션 내의 공간 크기를 조절하는 방법을 출력해준다
GC 가 수행되면서 크기가 조정되는 것을 볼 수 있다
힙 덤프 분석
GC 로그 남기기
-XX:+PrintGCDetails (권장)
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
GC Histogram
힙 실시간 분석
jconsole
$ jstat -gcutil [process_id] 1000
==> 1000ms 마다 process_id의 힙 메모리 크기, YGC, FGC의 횟수를 출력한다
$ jmap -heap [process_id]
==> process_id의 현재 힙 정보를 출력
중요한 것은 사용하는 GC 알고리즘이 출력하는 로그를 분석할 줄 아는 능력이다.
다음부터 GC 알고리즘 하나하나 로그를 보는 법을 살펴보자!
[출처] [JVM] 기본 GC 튜닝
'JAVA > JVM' 카테고리의 다른 글
[JVM] CMS Collector - 힙 사이즈 / Concurrent Mode 튜닝하기 (0) | 2021.12.16 |
---|---|
[JVM] CMS Collector - GC Log (0) | 2021.12.16 |
[JVM] Throughput Collector - 힙 사이즈 튜닝하기 (0) | 2021.12.16 |
[JVM] Throughput Collector - GC Log (0) | 2021.12.16 |
[JVM] GC 알고리즘 - Overview (0) | 2021.12.16 |
[JVM] JIT 컴파일 로그, 컴파일 스레드, 인라이닝 튜닝 (0) | 2021.12.16 |
[JVM] JIT : Just In Time Compiler 개념 & 튜닝 (0) | 2021.12.16 |
[JVM] PERM 영역 이해하기 (0) | 2021.12.16 |