C는 포인터를 통해 컴퓨터 메모리를 직접 참조할 수 있다. 하지만 Java는 JVM이 컴퓨터의 메모리의 공간을 할당받아 프로그램을 동작하며 컴퓨터의 메모리주소를 직접 참조할 수 없다. C언어는 동적으로 메모리를 할당한 후 사용이 끝나면 반드시 해제해주어야 한다. 반면 Java는 개발자가 직접 해제해줄 필요 없이 JVM 내부에서 Garbage Collection(이하 GC)이란 과정을 통해 메모리를 해제한다.
JVM의 메모리 관리 방식으로 Heap영역에 할당받은 메모리 공간을 주기적으로 정리하는 과정을 말한다. 개발자가 직접 Heap 영역에 할당한 메모리를 해제해주지 않아도 JVM 내부에서 주기적으로 정리한다. 물론 개발자가 GC를 직접 실행할 수 있지만 권장하지 않는다. 또한 개발자가 GC의 옵션을 수정하여 프로그램의 성능향상을 기대할 수 있다.
줄여서 STW라고 한다. GC를 실행할 때 GC를 실행중인 쓰레드를 제외한 모든 쓰레드는 동작을 멈춘다. 물론 GC의 전체 과정에서 항상 STW가 발생하는 것은 아니고 특정 단계에서 발생한다. STW 시간이 길어질 수록 프로그램의 성능은 낮아진다. 즉 너무 자주 GC를 실행시키면 프로그램이 느려질 수 밖에 없다. 따라서 Garbage Collector는 STW를 줄이는 방향으로 발전하고 있고, 개발자는 GC가 덜 발생하는 방향으로 개발을 진행해야한다.
개발자가 GC를 직접 실행하는 것은 매우 위험하다. JVM 내부에서 때가 되면 알아서 GC를 실행할 것이다. 미사일을 발사하기 직전에 GC가 실행되어 STW가 발생한다면 정말 위험할 것이다. 아주 섬세한 실시간 시스템을 사용하는 곳에서 Java를 사용할 때는 주의해야한다.
GC를 수행하는 Garbage Collector는 여러 종류가 있고 지속적으로 개발 보완되고 있다. 이 Garbage Collector들이 이용하는 몇가지 알고리즘을 알아보자.
최상위 진입점인 Root Space에서 Heap 공간을 참조하는 방향으로 순회한다. 이 때 Heap 내부의 Object가 참조될 때 마다 Reference Count가 1씩 증가한다. 만약 Reference Count가 0이면 GC의 대상이 된다.
최상위 진입접인 Root Set에서 Heap 내부 공간을 순회한다. 이 때 참조되는 Object를 Reachable Object 그렇지 않은 경우를 Unreachble Object라고 한다. 이렇게 Heap 내부 공간을 순회하며 Reachable, Unreachable로 Object를 표시하는 과정을 Mark이라고 한다. Marking이 완료되면 Unreachable Object를 정리하면된다. 이 과정을 Sweep이라고 한다.
Mark and Sweep의 메모리 단편화 문제를 해결하기 위해 나온 알고리즘이다. Mark and Sweep을 수행한 후 Compact라는 과정이 추가됐다. Compact 과정은 Sweep 이후 흩어져있던 Reachable Object들을 한 곳으로 모으는 과정을 의미한다. 메모리 공간을 효율적으로 관리할 수 있다는 장점이 있다.
Generational 알고리즘을 이용한 GC는 위 2가지 가정으로 만들어졌다. 잠깐 사용되고 금세 버려지는 객체를 Young 영역에 할당하고, Young 영역에서 오래 살아남은 객체를 Old 영역으로 이동시켜 관리하는 방식이다.
Major GC는 Minor GC보다 훨씬 큰 오버헤드를 수반하므로 개발자가 GC를 튜닝할 때 Old 영역과 Young 영역의 비율을 상황에 맞게 조절할 수 있다.
이번에는 GC가 무엇인지 GC를 수행하는 알고리즘들에 대해 알아봤다. 다음시간에는 Serial GC부터 Z GC까지 GC 변천사에 대해 포스팅하겠다.