[JVM] G1 Collector - 더! 큰 객체 할당

2021. 12. 16. 16:58 JAVA/JVM

큰 객체 할당, TLAB 요약
TLAB( Thread Local Allocation Buffer ) 보다 큰 객체들은 TLAB 바깥에 할당되서 TLAB 안에 할당되는 객체보다 처리가 느리다.
TLAB 은 스레드 내의 로컬 영역으로, TLAB 안에 할당된 객체는 스레드 동기화 등의 추가적인 처리를 하지 않고 로컬 변수처럼 처리할 수 있기 때문에 더 빠르다.
TLAB 은 에덴내의 한 영역으로 에덴의 크기에 좌우된다.
TLAB 을 튜닝하려면 JFR을 사용해서 TLAB 밖에 할당되는 객체들의 크기를 확인한 뒤, TLAB이 최대한 많은 객체들을 포함할 수 있도록 TLAB의 크기를 늘리면 된다. 
주의할 점은 JFR의 TLAB 확인 기능은 어플리케이션에 많은 부하가 발생해서 기본적으로 꺼져있으며 프러뎍선 환경에서 사용하면 안된다. 


더~ 크다? 거대하다?
TLAB 밖에 할당되는 객체들도 여전히 한 에덴 내에 할당된다.
하지만 객체의 크기가 남은 에덴 영역의 크기보다 크다면? 올드 제너레이션으로 바로 할당된다. 
TLAB 에도 할당되지 못하고, 남은 에덴 영역에도 할당되지 못하는 객체들을 거대하다고 한다. 
거대한 객체들은 일반적인 GC 사이클을 거치지 않고 바로 올드 제너레이션에 할당되며, 때문에 Full GC가 발생할 가능성이 당연히 상승하게된다.


G1 Region (영역)
G1외의 GC 알고리즘에서는 어플리케이션 코드를 변경하는 것 밖에 할 수 있는 일이 없다. 
G1은 조금 다르게 처리한다. G1은 G1영역이라는 것이 있고, G1영역의 크기보다 더 큰 객체를 올드 제너레이션에 할당한다.

G1은 힙의 고정된 크기를 가진 영역 여러 개로 나눈다.  
영역의 크기는 고정되어있고, 영역의 크기는 처음 시작할 때 힙의 최소 크기 Xms 값을 기반으로 결정된다.
영역의 최소 크기는 1MB이다. 영역의 최대 크기는 32MB 이다.
최소 힙의 크기 Xms값이 2GB보다 크다면 영역의 크기는 다음 공식을 따른다. 

영역의 크기는 거의 2048개에 이른다
계산된 값은 아래와 같다.


-XX:G1HeapRegionSize = N ( Default : 0 )
G1 영역의 크기를 결정한다. 영역의 크기는 2승이어야 하며, 그렇지 않으면 2승으로 반올림된다.


G1 영역은 많을수록 좋다? NO
-Xms2G -Xmx32G일 경우 영역 크기는 1MB으로 결정되며 힙이 완전히 확장되어 32GB일 경우 32,000개의 G1 영역이 생기게 된다. 
G1은 영역의 개수가 2048개에 최적화되어 설계되었기 때문에, 너무 많은 영역은 오히려 좋지 않다. 


거대한 객체 할당
G1 영역의 크기가 1MB이고 할당되는 배열 객체가 1MB의 크기를 넘어선다면 배열은 연속적인 G1 영역에 할당되야 한다.
연속적인 공간이 없으면 연속적인 공간을 만들기 위해 Full GC를 수행한다!
거대한 객체는 올드 제너레이션에 직접 할당되기 때문에 Minor GC가 발생해도 해제시킬 수 없다. 
거대한 객체는 Concurrent GC 동안 수집된다.
G1 영역의 크기를 늘려서 객체가 단일 영역 내에 들어가게 만들면 좀 더 효율적이다. 


거대한 객체 할당 로그
거대한 객체 할당이 실패해서 Full GC 가 발생할 경우 아래와 같이 로그가 남는다


거대한 객체 할당 튜닝하기
1) 로그를 살펴서 거대한 객체의 크기를 확인한다
allocation request : 524304 bytes...
2) JVM을 튜닝하기 보다 어플리케이션 코드를 수정해서 거대한 객체가 발생하지 않도록 한다. 
(대량 업로드일 경우 나눠서 업로드하는 방식으로)
3) 어플리케이션 코드 수정이 불가능하다면, 영역의 크기를 늘리자
- 거대한 객체의 크기가 524304 byte라고 하면 넉넉하게 2배는 더 줘야한다. 그렇지 않으면 영역이 완전히 비어있어야만 거대한 객체가 할당되기 때문에 비효율적이다. 
- 따라서 필요한 영역의 크기는 1.1MB정도이고 G1영역은 항상 2승으로 표현되므로 2MB로 설정해야 한다.
- 마찬가지로 G1영역이 1MB 이면 어플리케이션의 거대한 객체는 512KB정도가 된다. 이보다 증가하면 G1영역을 늘리거나 거대한 객체를 작게 만들 필요가 있다.