Java의 경우, C++과 다르게 더 이상 필요하지 않은 메모리를
자동으로 정리해준다.
Java를 사용하게 되면, 메모리 해제의 영역은
더 이상 개발자의 몫이 아니게 된다.
(System.gc()를 호출해 명시적으로 GC를 실행할 수 있지만,
성능 저하의 원인이 됨. 이유는 아래에 설명)
그렇다고 해서 메모리 관리에 신경쓰지 않아도 되는 것은 아니다.
Stop-the-world의 잦은 발생은 성능 저하의 큰 원인이기 때문에
Garbage Collection이 작동하는 방식에 대해서 알 필요가 있다.
JVM의 Garbage Collector가 필요하지 않은 메모리를 어떻게 구별하여 정리하는지,
Stop-the-world는 무엇인지, Garbage Collection에 대해 조금 더 자세히 알아보자.
더 이상 필요하지 않게 된 메모리를 정리해주는 기법
Garbage Collector가 heap 내의 객체 중 더이상 필요하지 않은 객체를 찾아
객체에 해당하는 힙 메모리를 회수한다.
겉보기에는 성능 저하와 연관이 있어 보이지 않는다.
오히려 자주 Garbage Collection을 통해 메모리를 정리하면
성능이 향상될 수 있을 것이라 예상할 수 있다.
앞서 언급한 Stop-the-world의 의미를 살펴보자.
이는 GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것을 의미한다.
Stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한
모든 쓰레드의 동작이 일시적으로 정지된다.
그렇기에 잦은 Stop-the-world의 발생은 속도 저하를 일으키는 원인이 되는 것이고,
GC 실행시간을 적절히 조절하는 것이 중요하다.
그렇다면 더 이상 필요하지 않은 메모리인 Garbage를
어떻게 찾아내는지 알아보자.
GC의 경우 어떤 객체에 대해 유효한 참조가 존재하면 reachable,
존재하지 않으면 unreachable로 구별한다.
그리고 이 unreachable 객체를 Garbage로 간주하고 메모리를 회수한다.
(참조에는 strong reference, soft reference, weak reference, phantom reference 등 세분화된 부분이 존재함. 아래에서 설명)
JVM의 메모리 영역인 런타임 데이터 영역의 구조를 살펴보자.

Oracle HotSpot VM을 기준으로 한 메모리 영역 구조이다.
해당 그림에서 객체에 대한 참조가 화살표로 표현되어 있다.
객체에 대한 참조가 존재하는 경우의 수는 다음과 같다.
- 힙 내의 다른 객체에 의한 참조
- Java 메서드 실행 시 사용되는 지역 변수, 파라미터에 의한 참조
- JNI에 의해 생성된 객체에 대한 참조
- 메서드 영역의 정적 변수에 의한 참조
여기서 참조된 객체가 또 다른 객체를 참조하는 경우와 같이 객체의 참조 사슬이
형성될 수 있다.
이러한 경우, 유효한 참조 여부 파악을 위해 유효한 최초의 참조가 필요한데
이를 객체 참조의 root set이라고 한다.
이 root set을 기준으로 객체의 reachable/unreachable을 판단할 수 있다.
root set과 힙 내의 객체를 중심으로 그려진 그림은 다음과 같다.

root set에서 시작한 참조 사슬에 속한 객체들은 reachable 객체이고,
이 참조 사슬과 무관한 객체들이 unreachable 객체로, GC 대상이다.
오른쪽 하단의 객체의 경우 reachable 객체를 참조하고 있지만
다른 reachable 객체가 이 객체를 참조하고 있지 않으므로
해당 객체는 unreachable 객체이다.
이러한 일반적인 참조의 경우 strong reference라고 부르며,
java.lang.ref 패키지를 사용하지 않은 참조이다.
해당 패키지에는 위에서 언급했던 참조 중
soft reference, weak reference, phantom reference가 포함된다.
굳이 참조에 종류가 존재하는 이유가 있을까 싶지만
해당 참조들을 사용하면 GC에 일정 부분 관여할 수 있게 된다.
이에 대해 조금 더 자세히 알아보자.
WeakReference 클래스를 사용해 객체를 생성해보자.
WeakReference<Sample> wr = new WeakReference<Sample>(new Sample());
Sample ex = wr.get();
ex = null;
위의 코드에서 WeakReference 클래스의 객체는
new() 메서드로 생성된 Sample 객체를 캡슐화한 객체이다.
참조된 Sample 객체는 두 번째 줄에서 get() 메서드를 통해 다른 참조에 대입된다.
이 시점에서 WeakReference 객체 내의 참조와 ex 참조,
두 개의 참조가 다음과 같이 처음 생성한 Sample 객체를 가리킨다.

여기서 ex 참조에 null을 대입하면 처음 생성한 Sample 객체는
다음과 같이 오직 WeakReference 내부에서만 참조된다.

이 상태의 객체를 weakly reachable 객체라고 하는데,
이에 대해서는 조금 아래에서 더 자세히 살펴보자.
Java 스펙에서는 SoftReference, WeakReference, PhantomReference의
3가지 클래스에 의해 생성된 객체를 reference object라고 부른다.
그리고 reference object에 의해 참조된 객체는 referent라고 부른다.
원래 GC 대상 여부는 reachable / unreachable로만 구분했고
이를 사용자 코드에서는 관여할 수 없었다.
이제는 java.lang.ref 패키지를 이용해 reachable 객체들을
strongly reachable, softly reachable, weakly reachable, phantom reachable
로 더 자세히 구분해 GC의 동작을 상황에 따르게 다르게 지정할 수 있게 되었다.
즉, GC 대상 여부를 판별하는 부분에 사용자 코드가 개입할 수 있게 되었다.
앞서 언급한 그림 중, 몇몇 객체를 WeakReference로 바꾸어보면 다음과 같다.

녹색으로 표시된 객체는 WeakReference로만 참조된 weakly reachable 객체이고,
파란색 객체는 strongly reachable 객체이다.
여기서 녹색 객체는 root set으로부터 시작된 참조 사슬에 포함되어 있지만,
가비지 객체로 간주되어 GC에 의해 메모리에서 회수된다.
즉, weakly reachable 객체는 GC의 대상이 된다.
그러므로 이를 이용해 참조는 가능하지만 반드시 항상 유효할 필요는 없는
LRU 캐시와 같은 임시 객체들을 저장하는 구조를 쉽게 만들 수 있다.
그림을 살펴보면 WeakReference 객체 자체는 strongly reachable 객체이다.
그리고 A의 경우 WeakReference에 참조되며 동시에
root set의 참조 사슬에 포함되므로 strongly reachable 객체이다.
정리하자면, 하나의 객체는 다음의 5가지 reachability 중 하나가 될 수 있으며
이 5종류의 reachability를 Strengths of Reachability라고 한다.
- root set에서 시작해 어떤
reference object도
중간에 포함되지 않은 상태로 참조된 객체
참조 사슬 내에서 reference object가 하나도 없는 사슬이
하나라도 존재할 경우를 말한다.
- strongly reachable 객체가 아닌 객체 중
weak reference, phantom reference 없이 soft reference만 참조하는 객체
참조 사슬 내에서 soft reference만 존재하는 사슬이
하나라도 존재할 경우를 말한다.
힙에 남아 있는 메모리 크기와 해당 객체의 사용 빈도에 따라
GC 여부가 결정된다.
JVM을 통해 옵션을 설정해 GC 여부를 결정할 수 있다.
어떤 객체가 사용된다는 것은 strong reference에 의해 참조되는 것이다.
옵션 설정 값에 따라 객체가 n초 이상 사용되지 않으면
GC에 의해 회수 대상이 된다.
GC 대상이 되면, softly reachable 객체에 대한 참조가 null로 설정되며
해당 객체는 unreachable 객체와 마찬가지로 GC에 의해 메모리가 회수된다.
그러므로 GC가 동작할 때마다 회수되는 객체는 아니다.
strongly reachable, softly reachable 객체가 아닌 객체 중
phantom reference 없이 weak reference만 참조 영역에 존재하는 경우
참조 사슬 내에 weak reference만 존재하는 사슬이
하나라도 존재하는 경우를 말한다.
항상 GC의 대상이 된다.
- 위의 모든 reachable 객체에 해당되지 않은 객체
이 객체는 finalize 되었지만 아직 메모리가 회수되지 않은 상태이다.
- root set으로부터 시작되는 참조 사슬로 참조되지 않은 객체
예를 들어, 다음 그림에서 객체 B는 softly reachable 객체이다.

root set을 통해 바로 SoftReference를 통해 B를 참조할 수 있기 때문이다.
만약 왼쪽 아래 화살표(Soft Reference 왼쪽의 화살표)를 삭제한다면,
객체 B는 phantomly reachable이 된다.
reference 객체에 대해 어느 정도 알아보았는데,
Phantom Reference가 사용될 경우가 흔하지 않아 따로 해당 내용을 작성하지는 않았다.
GC가 실제로 언제 객체를 회수할 지는 GC 알고리즘에 따라 모두 다르다.
GC가 수행될 때마다 반드시 메모리까지 회수된다고 보장할 수는 없다.
GC가 GC 대상 객체를 찾는 작업과
GC 대상인 객체를 처리해(finalization) 메모리를 회수하는 작업은
즉각적인 연속 작업이 아니다.
또한, GC 대상 객체의 메모리를 한 번에 모두 회수하는 것도 아니다.
다음 글에서는 GC 알고리즘의 종류 및 흐름도 등에 대해 알아보자.