JVM 시리즈 - 메모리 할당 전략
자동 메모리 관리
자동 메모리 관리는 객체의 메모리를 자동으로 할당하고, 객체에 할당된 메모리를 자동으로 회수한다. 회수에 대해서는 앞선 글에서 다뤘다. 이 글에서는 객체 메모리 할당에 대해서 다뤄보겠다.
객체 메모리 할당은 객체를 힙에 할당하는 것을 뜻하며, 이는 명세에 의해 규정된 규칙이 아니라 각각의 가비지 컬렉터와 메개 변수 설정값에 따라서 다른 방법으로 구현된다.
객체는 에덴에
대부분의 객체는 신세대의 에덴에 할당되며, 에덴의 공간이 부족해지면 마이너 GC 가 일어난다. 객체를 생성하고, GC 의 발생 결과를 확인해보자.
1
2
3
4
5
6
7
8
9
public static void testAllocation(){
byte[]alloc1,alloc2,alloc3,alloc4;
alloc1=new byte[2*_1MB];
alloc2=new byte[2*_1MB];
alloc3=new byte[2*_1MB];
alloc4=new byte[4*_1MB]; // 마이너 GC 발생
}
위 코드의 실행 결과는 가상 머신의 -Xlog:gc 매개 변수를 통해서 로그를 확인할 수 있다. 런타임 힙 크기를 20mb 로 제한한 결과이다.
1
2
3
4
5
DefNew:729K->672K // GC 결과(신세대)
Eden:729K->0K
Tenured:0K->6144K // GC 결과(구세대)
신세대에 10mb, 구세대에 10mb 가 할당됐고, 신세대에 중에서도 생존자 공간과 에덴의 비율이 1:8 로 설정됐다. 마이너 GC 가 수행되면, 생존자 공간으로 객체를 옮기기에는 공간의 한계가 존재함을 발견하고 구세대로 객체가 옮겨지는 것을 알 수 있다.
큰 객체는 구세대에
큰 객체는 연속된 메모리 공간을 필요로 한다. 연속된 공간을 확보하기 위해서 다른 객체들을 옮겨줘야 하는데, 이는 메모리 복사 오버헤드를 동반한다.
1
2
3
--XX:PretenureSizeThreshold
매개 변수를 설정하면 설정값보다 큰 객체를 구세대에 할당하는데, 이는 에덴과 두 생존자 공간 사이의 복사를 줄일 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// --XX:PretenureSizeThreshold=3M
public static void testPretenureSizeThreshold(){
byte[]alloc;
alloc=new byte[4*_1MB]; // 구세대 할당
}
// 실행 결과
def new generation total 9216K,used 1312K
eden space 8192k,16%used
from space 1024k,0%used
to space 1024k,0%used
tenured generation total 10240K,used 4096k
에덴은 쓰이지 않고, 10mb 객체가 곧바로 구세대에 할당됐음을 알 수 있다.
나이가 차면 구세대로
객체 헤더의 세대 나이 카운터를 통해서 객체는 신세대에서 구세대로 이동된다. 에덴에서 태어난 객체는 생존자 공간을 거쳐서 구세대로 승격되는 것이다.
1
2
3
--XX: MaxTenuringThreshold
매개변수 설정을 통해서 구세대 이동 시기를 설정할 수 있다. GC 발생에서 몇번을 거쳐야 구세대로 승격되는 것인지 결정할 수 있다. 그러나 꼭 해당 횟수를 채워야지만 이동하는 것은 아니고, 생존자 공간이 (기본값 80%) 일정 비율 이상 찬다면 구세대로 옮겨진다.