JVM GC 후 Heap 에게 할당된 물리 메모리 OS에게 회수 여부
https://woooongs.tistory.com/85
위 글에서 최소 힙 사이즈보다 사용중인 물리 메모리가 더 작은 이유에 대해서 확인해보았다.
JVM 은 가상메모리에는 최소 힙 사이즈 만큼 공간을 할당하지만 실제로 객체를 힙에 저장하기 전까지는 물리 메모리를 점유하지 않는다.
하지만 위 글에서 해결되지 않는 의문이 있었다.
객체가 실제로 힙에 저장될때 Java process RSS 사용량이 늘어나지만 GC 가 발생했을때는 RSS 가 줄어들지 않는 것이다.
어떤 이유에서인지 정확하게 알지 못하지만 JVM 에게 할당된 물리 메모리는 GC 가 발생한다고 하더라도 OS 에게 즉시 반환되지 않는다고 한다.
이 내용을 확인하고자 테스트를 진행하였다.
테스트 코드 및 실행 커맨드
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
public static void main(String[] args) throws InterruptedException {
for (int j = 0 ; j < 100; j++) {
ArrayList<char[]> arrayList = new ArrayList<>();
for (int i = 0 ; i < 10; i++) {
Thread.sleep(1000);
System.out.println("Count : " + i);
char[] chars = new char[1000000];
Arrays.fill(chars, (char) i);
arrayList.add(chars);
}
Thread.sleep(5000);
System.out.println("GC Performed");
}
}
}
java -Xms30m -Xmx30m -verbose:gc -XX:+UseG1GC Main
-Xms 와 -Xmx 값은 30M 으로 Heap 사이즈는 30MB 고정.
반복문을 돌면서 약 1M char 배열을 10번 생성후 GC 가 일어나도록 참조를 잃는다.
테스트 결과
터미널1) jstat 명령어로 GC 로 인해 각 영역의 메모리 사용량이 줄어드는것을 확인
터미널2) top 명령어로 자바 프로세스의 물리 메모리 점유량 (RES) 를 확인
터미널3) GC 로그 모니터링
00:00) 자바 프로세스를 실행하자마자 처음 찍힌 top 명령어의 RES 는 26MB 이다.
-Xms, -Xmx 모두 30MB 이지만 Java Process 가 사용하는 물리 메모리는 26MB 로 Heap 사이즈만큼 물리 메모리를 점유하지 않는것을 확인할 수 있다.
00:23) 객체를 할당함에 따라 RES 가 계속 증가하는것을 확인할 수 있다. 이를 통해 힙에 객체가 할당될때 JVM 이 물리 메모리를 점유하고 RES 가 증가하는것을 확인할 수 있다. 약 52MB 까지 사용량이 증가하였다.
Old Generation 에 객체들이 계속 쌓이고 있음을 확인할 수 있다.
00:37) 첫번째 Full GC 발생. GC 로그에서 알 수 있지만 Heap 영역 사용량이 25MB -> 5.9MB 로 줄어들었다.
GC 이전 - Old영역 사용량 : 24MB
GC 이후 - Old영역 사용량 : 8.8MB
GC 이후에 분명히 힙 영역의 공간이 확보되었다. 하지만 Java process 의 RES 를 보면
01:06) Full GC 가 한번 더 발생하였다. GC 에 의해서 분명히 Heap 영역의 공간이 확보된 것을 확인할 수 있다.
하지만 마찬가지로 JAVA process 의 RES 는 줄지 않았다. 또한 시간이 경과됨에 따라 꾸준히 증가하던 RES 는 거의 증가하지 않는 상태가 되었다.
결론
이 테스트로 확인해보고 싶었던 것은 JVM 이 힙에 객체를 할당할때 물리 메모리 사용량이 증가하지만 해당 객체가 GC 가 되었을때 JVM 이미 점유한 물리 메모리는 OS 에게 반환되지 않는다는 것을 확인하고 싶었다. 실제로 GC 가 발생하여도 물리 메모리 사용량은 줄어들지 않았다. 그렇다는 것은 JVM 은 힙 영역을 사용하기 위해 한번 할당 받은 물리 메모리에 객체를 다시 할당하여 쓰고 있는것으로 보인다.
왜 JVM 이 한번 할당 받은 메모리를 OS 에게 반환하지 않는지는 더 확인을 해보아야 겠다. 테스트를 진행한 Linux 는 Swap 이 되지 않도록 설정되어있어서 swap out 되지 않기 때문일수도 있고 JVM 이 Heap 에 접근을 자주하니 Working Set Model 에 의해서 자주 접근되는 Heap 이 할당된 Frame 은 제거 되지 않았을 수도 있을 것 같은데 조금 더 확인이 필요하다.
번외
Heap 은 30MB 뿐인데 물리 메모리 사용량은 50MB 이상 나오니 어떤 것이 메모리를 사용하고 있는지 확인하고싶었다.
pmap -x 19408 | sort -k 3 -n -r | more
total kB 3583588 59124 41200
00000000fe200000 31232 30368 30368 rw--- [ anon ]
00007f16aee8d000 13760 11572 0 r-x-- libjvm.so
00007f16492fb000 4352 2516 2516 rw--- [ anon ]
00007f164912a000 1860 1860 0 r--s- rt.jar
00007f16b052e000 1948 1580 0 r-x-- libc-2.27.so
00007f16a8000000 1672 1412 1412 rw--- [ anon ]
00007f16ac019000 9068 880 880 rw--- [ anon ]
00007f161c000000 1044 852 852 rw--- [ anon ]
00007f169802d000 3868 796 796 rw--- [ anon ]
00007f16aeb04000 1508 744 0 r-x-- libstdc++.so.6.0.25
Java process 가 사용하는 메모리를 확인한 것인데 가장 상단에 있는 내역이 Heap 메모리이다.
VIRT = 30.5MB , RES = 29.6MB 로 설정한 대로 30MB 이내로 동작중이였으며 하위에 메모리에 올라가있는 libjvm.so 와 같은 라이브러리들이 메모리를 사용하고 있는 것을 볼 수 있다.