
JVM의 GC는 heap 영역의 객체들 중에서 가비지(더 이상 참조되지 않는 객체)를 찾아 처리함으로써 할당했던 메모리를 회수하는 역할을 한다.
하지만 GC는 메모리 관리의 일관성과 안정성을 보장하기 위해 외부의 영향을 최대한 제한하는 구조로 설계되어, java.lang.ref 패키지의 참조 객체 관련 요소들로만 GC에 제한적으로 어필이 가능하다.

기본적인 참조 방식인 "강한 참조"는 별도의 클래스로 제공되지 않으며, 특수한 참조 방식인 Phantom/Soft/Weak Reference가 존재하는 것을 볼 수 있다. 우리는 이 참조 방식들을 사용해서 GC의 동작에 제한적이나마 관여할 수 있게 된다.
1. Garbage Collector가 참조를 처리하는 방법
GC는 가비지를 찾아 처리하고 메모리를 회수하는 목적을 가지는데, 어떤 기준으로 가비지를 찾아낼까?
JVM의 GC는 어떤 객체에 "도달할 수 있는가(reachable)" 여부에 따라 가비지를 판단한다.
앞선 포스팅에서 R8 컴파일러도 "Unreachable"한 코드들에 대해 코드 축소를 진행했던 것을 생각해보면, Dirty나 reachable과 같은 표현을 사용하는 방식들(?)은 꽤 자주 볼 수 있으며 그 방식들 또한 대부분 비슷하다.
이 "도달 가능성 판단(Reachable한가?)"의 메커니즘은 아래와 같다.
(1) GC Root 설정
어떤 객체에 도달하는 과정에는 시작점이 필요한데, 이를 위해 항상 유효한 참조를 가지는 요소들을 Gc Root라고 부른다.
- 현재 실행중인 스레드의 스택 영역에 저장된 지역 변수와 메소드의 파라미터
- 정적 필드(클래스 로딩 시에 메모리에 할당되어 종료시까지 상주하는)
- 이외에, 네이티브 코드에서 관리되는 객체, JVM의 동작에 필수적인 객체들

위 그림처럼 Gc root 요소들이 참조하는 Heap 영역 내부의 객체는 reachable한 객체가 되고,
추가적으로 Heap 영역 내부의 reachable한 객체들이 참조하는 다른 객체들 또한 reachable한 객체가 된다.
unreachable한 객체들을 보면, Gc root 요소로부터 참조되지 않으며 다른 reachable한 객체들로부터도 참조되지 않는 것을 볼 수 있다. 참조 관계는 reachable 객체가 다른 객체를 참조해야 유효하며, 다른 객체가 일방적으로 reachable한 객체를 참조한다고 해도 reachable해지지 않는다.
하지만 위 그림에서는 강한 참조(strong reference)만을 표현하고 있다.
특수한 참조 관계를 사용하면 GC가 객체들의 reachable 여부를 어떻게 판단하게 될까?
2. java.lang.ref 패키지의 Reference 서브 클래스들
✨ WeakReference
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
참조 대상을 약한 참조로 감싸는 참조 객체이다.
내부 객체는 Weak Reference에 의해 참조되며, GC는 이 Weak Reference를 Strong Reference와는 다르게 취급하게 된다.
fun WeakReferenceExample() {
val weakThing: WeakReference<Something> = WeakReference(Something("I'm weak"))
var innerThing: Something = weakThing.get()
innerThing = null
}
위 코드는 WeakReference 클래스를 사용해서 객체를 생성하는 예시 코드이다.
이해를 돕기 위해 위 코드의 흐름을 그림으로 표현해보자.

우선, Something 객체를 감싸는 WeakReference 객체를 생성한 상태이다.
이 상태에서 Something 객체는 두 개의 참조를 가지게 된다.
- Heap 영역의 WeakReference 객체에 의한 참조
- get() 메서드를 통해 innerThing에게 참조

이후 innerThing에 null을 대입하게 되면 Something 객체는 Heap 영역의 WeakReference에 의해서만 참조된다.
이처럼 WeakReference에 의해서만 참조되는 객체를 weakly reachable 객체라고 하며, weakly reachable 객체는 GC가 수행될 때마다 회수 대상이 된다.
일반적인 객체는 사용이 끝난 시점에 개발자가 명시적으로 강한 참조를 해제해줘야 하지만, 여러 곳에서 참조하고 있거나 생명주기가 복잡한 경우에는 명확한 처리가 누락될 수 있다.
그런 경우에 WeakReference를 사용하여 객체를 래핑하면, 해당 객체에 강한 참조가 없을 경우 GC가 이를 감지하고 회수 대상으로 지정할 수 있도록 "객체의 생명주기 관리를 자동화"할 수 있다.
✨ SoftReference

SoftReference 또한 위의 WeakReference처럼, 객체가 강한 참조 없이 SoftReference 객체에 의해서만 참조되는 있는 경우를 softly reachable 객체라고 한다.
하지만 softly reachable 객체는 Heap에 남은 메모리의 크기와 해당 객체의 사용 빈도에 따라 GC의 회수 대상이 되는지가 결정된다는 점이 다르다. 객체가 자주 사용될 수록, 메모리가 여유로울수록 GC의 대상이 되는 빈도가 줄어든다.
메모리의 영향을 받아 GC의 대상이 되므로 weakly reachable 객체와는 다르게 GC 동작 시마다 회수 대상으로 지정되지 않아 객체가 더 긴 생명주기를 가진다. 만약 메모리가 부족하다고 판단될 경우, softly reachable 객체를 우선적으로 회수해서 메모리를 확보하게 된다.
✨ PhantomReference
PhantomReference에 대한 내용은 아직 이해도가 부족하여 간략하게만 적었습니다. 아직은 너무 어려운ㅠ
PhantomReference는 객체가 GC에 의해 실제로 회수되는 상태를 추적하기 위해 사용된다. 객체가 "더 이상 어떤 방식으로도 유효한 참조될 수 없는 상태"가 되는 상태를 감지하는 용도로 설계되었다.
- GC 시 회수 대상이지만, 회수 대상으로 지정되는 것과 실제로 회수가 되는 과정은 연속적으로 이루어지지 않기 때문에, "실제로 회수가 될 때" 특정 작업을 하기 위해 사용된다.
- get() 함수를 호출해서 참조하는 객체를 가져와 사용하는 Weak/Soft Reference 객체들과 다르게, get() 함수를 호출하면 항상 null을 반환한다.
- ReferenceQueue와 함께 사용된다. 객체가 GC의 회수 대상이 될 때, ReferenceQueue에 추가되는 것으로 "객체가 회수되는 중"이라는 이벤트를 감지할 수 있다.
예전에 메모리 관리와 관련한 포스팅을 읽은 적이 있다. Bitmap의 메모리 할당 해제와 관련된 글이였는데,
finalize() 메서드가 "GC에 의해 객체가 회수되는 시점에 특정 로직을 수행하기 위해 사용한다"라고 되어 있었다.
이는 PhantomReference와 동일한 목적이기 때문에, PhantomReference가 finalize 메서드를 대체하기 위해 만들어졌다는 것을 알 수 있다.
그 포스팅에서 finalize 메서드를 오버라이드할 경우에 객체가 회수 대상이 됨에도 즉시 호출되지 않는다는 문제가 있었다고 해서 Object 클래스의 finalize 메서드를 간단하게 살펴보았다.
// A subclass should avoid overriding the finalize method unless the subclass
// embeds non-heap resources that must be cleaned up before the instance is collected.
@Deprecated(since="9")
protected void finalize() throws Throwable { }
finalize 메서드는 Java 9 이후로 Deprecated되었으며, Heap 영역 외부의 리소스를 해제해야하는 경우가 아니라면 오버라이드를 권장하지 않는 것을 볼 수 있다.
그래서 PhantomReference를 만들어서 finalize의 문제를 개선하면서 사용자의 코드가 GC에 좀 더 관여할 수 있도록 개선된걸까?
PhantomReference는 finalize보다 더 예측 가능하고 안전하다는데, 난 둘 다 한 번도 사용해 본 적이 없어서 비교를 해 볼 기회가 없을 것 같다...
3. Reachable 상태

도달 가능성(Reachablility)는 위처럼 5가지로 나누어 진다.
(1) Strongly Reachable
GC Root 요소와 객체 사이에 다른 Reference 객체가 존재하지 않는 상태로 참조되는 상태이다.
(2) Weakly Reachable
GC Root 요소에서 강한 참조나 SoftReference 객체를 통해서 도달할 수 없고, Weak Reference 객체를 통해서만 도달할 수 있는 상태이다.

위 그림에서는 WeakReference와 A 객체 사이에 SoftReference 객체가 존재하기 때문에, 객체 A는 softly reachable 상태로 간주된다.
(3) Softly Reachable
Softly Reachable 객체도 당연히 SoftReference 객체와 객체 사이에 다른 Reference 객체가 존재하지 않을 경우를 가리킨다. 근데 만약 WeakReference 객체와 SoftReference 객체가 같은 객체를 참조한다면, 이 객체는 어떤 상태를 가지게 될까?

정답은 softly reachable 상태인데, 그 이유는 GC가 참조 상태를 관리하기 위한 우선순위에 있다.
✨ GC의 Reachability 순위(강한 순)
- Strongly reachable
- Softly reachable
- Weakly reachable
- Phantomly reachable
- Unreachable
도달 가능성이 높을수록 객체가 더 오래 생존하므로, WeakReference에 의해 참조되고 있다고 해도 더 강한 참조인 SoftReference가 참조하고 있다면 더 긴 생명주기를 가지는 Softly reachable 상태로 간주된다.

만약 객체 A가 PhantomReference에 의해 참조되고 있다고 하더라도, 도달가능성에 따라 SoftReference보다 약한 참조로 간주되므로 A는 Softly reachable 상태가 될 것이다.
하지만 SoftReference에서 객체 A까지 도달하는 참조 체인 중에 Weak/Phantom Reference가 존재하면 객체 A는 Weakly/Phantomly reachable 상태가 된다.
그 대신 Weak/Phantom Reference 객체가 softly reachable 상태가 될텐데, 이 부분은 아직 너무 복잡해서 따로 학습하지 않았다.. 참조 객체를 참조 객체로 감싸는 방법은 아직 사용해 본적이 없다 ㅠㅠ
(4) Phantomly Reachable
객체를 가리키는 참조가 PhatomReference뿐일 때 Phantomly reachable 상태가 되며, 해당 객체는 더 이상 참조가 불가능해 진다.
(5) Unreachable
Gc Root 요소들로부터 시작되는 참조 체인으로 참조되지 않는 상태를 가리킨다. 이 요소들이 Gc Root 요소로 부터 참조되는 객체들을 역으로 참조한다 해도, Unreachable 상태로 간주되어 GC의 메모리 회수 대상이 된다.
❓WeakReference를 사용하면 GC가 돌 때 반드시 메모리에서 해제가 될까?
어느 면접 질문 리스트에서 가져온 질문인데, 이게 오늘 작성한 포스팅의 원래 주제였다.
WeakReference가 참조하는 객체가 weakly reachable 상태가 되면,GC는 약한 참조만 존재하는 객체를 회수 대상으로 인식한다. 그리고 수거 시 해당 객체에 대한 WeakReference의 참조를 null로 설정함으로써 수거가 이루어진다.
하지만 "회수 대상이 된다"가 "반드시 메모리에서 해제가 된다"로 직결되지는 않는다.
GC는 실행 시점과 메모리 해제 시점을 예측할 수 없으며, 내부 알고리즘이나 메모리 사용량에 따라 다음 GC 사이클에서 회수될 수도 있는 "비결정적"인 방식으로 동작하기 때문이다.
이처럼 WeakReference를 사용하면 필요할 경우 접근이 가능하면서, 객체를 GC의 회수 대상으로 만들 수는 있지만 GC 시에 즉각적인 메모리 회수는 보장되지 않는다.
✨ 번외) System.gc() 메서드 주석의 표현이 재밌다
우리가 System.gc() 를 호출해서 GC를 요청할 수는 있지만, 실제로 호출 시점과 GC 실행 시점은 예측할 수 없는 것도 GC의 비결정적인 특성 때문이다.
시스템의 모든 상황을 고려하지 못하는 우리가 "이걸 지금 GC 해야 해"라고 판단하는 것보다, 시스템이 자체적으로 판단하는게 더 효율적이기 때문이 아닐까라는 생각을 해 본다.
/*
* Runs the garbage collector.
* Calling the gc method suggests that the Java Virtual Machine expend effort toward
* recycling unused objects in order to make the memory they currently occupy available
* for quick reuse. When control returns from the method call, the Java Virtual Machine
* has made a best effort to reclaim space from all discarded objects.
*
* 해석해 보면,
* 가비지 콜렉터를 실행합니다.
* gc 메서드를 호출하면 Java Virtual Machine이 현재 차지하고 있는 메모리를
* 빠르게 재사용할 수 있도록 사용되지 않는 객체를 재활용하는 데
* 노력을 기울인다는 것을 의미합니다. 메서드 호출에서 제어가 반환되면
* Java Virtual Machine은 모든 폐기된 객체에서 공간을 회수하기 위해 최선을 다했습니다.
*/
public static void gc() {
int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion();
if (targetSdkVersion > 34) {
Runtime.getRuntime().gc();
} else {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = targetSdkVersion <= 34 ? justRanFinalization : true;
if (shouldRunGC) {
justRanFinalization = false;
} else {
runGC = true;
}
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
}
}
gc() 메서드의 주석에도 "최선을 다한다"는 표현을 사용한 이유는 결정적으로 동작하지 않아서이지 않을까!
위트 있는 주석이다.
'개발 > Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 가변성을 제한하라 (0) | 2025.04.09 |
---|---|
[Kotlin] sealed class, enum class 바이트 코드 뜯어보기 (1) | 2025.03.11 |
[Kotlin] 예외 처리 - 예상한 예상 밖의 결과 (0) | 2025.02.19 |
[이펙티브 코틀린] 프로퍼티는 동작이 아니라 상태를 나타내야 한다 (0) | 2025.02.07 |
[Kotlin] 타입 소거와 Star projection (0) | 2024.06.27 |

JVM의 GC는 heap 영역의 객체들 중에서 가비지(더 이상 참조되지 않는 객체)를 찾아 처리함으로써 할당했던 메모리를 회수하는 역할을 한다.
하지만 GC는 메모리 관리의 일관성과 안정성을 보장하기 위해 외부의 영향을 최대한 제한하는 구조로 설계되어, java.lang.ref 패키지의 참조 객체 관련 요소들로만 GC에 제한적으로 어필이 가능하다.

기본적인 참조 방식인 "강한 참조"는 별도의 클래스로 제공되지 않으며, 특수한 참조 방식인 Phantom/Soft/Weak Reference가 존재하는 것을 볼 수 있다. 우리는 이 참조 방식들을 사용해서 GC의 동작에 제한적이나마 관여할 수 있게 된다.
1. Garbage Collector가 참조를 처리하는 방법
GC는 가비지를 찾아 처리하고 메모리를 회수하는 목적을 가지는데, 어떤 기준으로 가비지를 찾아낼까?
JVM의 GC는 어떤 객체에 "도달할 수 있는가(reachable)" 여부에 따라 가비지를 판단한다.
앞선 포스팅에서 R8 컴파일러도 "Unreachable"한 코드들에 대해 코드 축소를 진행했던 것을 생각해보면, Dirty나 reachable과 같은 표현을 사용하는 방식들(?)은 꽤 자주 볼 수 있으며 그 방식들 또한 대부분 비슷하다.
이 "도달 가능성 판단(Reachable한가?)"의 메커니즘은 아래와 같다.
(1) GC Root 설정
어떤 객체에 도달하는 과정에는 시작점이 필요한데, 이를 위해 항상 유효한 참조를 가지는 요소들을 Gc Root라고 부른다.
- 현재 실행중인 스레드의 스택 영역에 저장된 지역 변수와 메소드의 파라미터
- 정적 필드(클래스 로딩 시에 메모리에 할당되어 종료시까지 상주하는)
- 이외에, 네이티브 코드에서 관리되는 객체, JVM의 동작에 필수적인 객체들

위 그림처럼 Gc root 요소들이 참조하는 Heap 영역 내부의 객체는 reachable한 객체가 되고,
추가적으로 Heap 영역 내부의 reachable한 객체들이 참조하는 다른 객체들 또한 reachable한 객체가 된다.
unreachable한 객체들을 보면, Gc root 요소로부터 참조되지 않으며 다른 reachable한 객체들로부터도 참조되지 않는 것을 볼 수 있다. 참조 관계는 reachable 객체가 다른 객체를 참조해야 유효하며, 다른 객체가 일방적으로 reachable한 객체를 참조한다고 해도 reachable해지지 않는다.
하지만 위 그림에서는 강한 참조(strong reference)만을 표현하고 있다.
특수한 참조 관계를 사용하면 GC가 객체들의 reachable 여부를 어떻게 판단하게 될까?
2. java.lang.ref 패키지의 Reference 서브 클래스들
✨ WeakReference
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
참조 대상을 약한 참조로 감싸는 참조 객체이다.
내부 객체는 Weak Reference에 의해 참조되며, GC는 이 Weak Reference를 Strong Reference와는 다르게 취급하게 된다.
fun WeakReferenceExample() {
val weakThing: WeakReference<Something> = WeakReference(Something("I'm weak"))
var innerThing: Something = weakThing.get()
innerThing = null
}
위 코드는 WeakReference 클래스를 사용해서 객체를 생성하는 예시 코드이다.
이해를 돕기 위해 위 코드의 흐름을 그림으로 표현해보자.

우선, Something 객체를 감싸는 WeakReference 객체를 생성한 상태이다.
이 상태에서 Something 객체는 두 개의 참조를 가지게 된다.
- Heap 영역의 WeakReference 객체에 의한 참조
- get() 메서드를 통해 innerThing에게 참조

이후 innerThing에 null을 대입하게 되면 Something 객체는 Heap 영역의 WeakReference에 의해서만 참조된다.
이처럼 WeakReference에 의해서만 참조되는 객체를 weakly reachable 객체라고 하며, weakly reachable 객체는 GC가 수행될 때마다 회수 대상이 된다.
일반적인 객체는 사용이 끝난 시점에 개발자가 명시적으로 강한 참조를 해제해줘야 하지만, 여러 곳에서 참조하고 있거나 생명주기가 복잡한 경우에는 명확한 처리가 누락될 수 있다.
그런 경우에 WeakReference를 사용하여 객체를 래핑하면, 해당 객체에 강한 참조가 없을 경우 GC가 이를 감지하고 회수 대상으로 지정할 수 있도록 "객체의 생명주기 관리를 자동화"할 수 있다.
✨ SoftReference

SoftReference 또한 위의 WeakReference처럼, 객체가 강한 참조 없이 SoftReference 객체에 의해서만 참조되는 있는 경우를 softly reachable 객체라고 한다.
하지만 softly reachable 객체는 Heap에 남은 메모리의 크기와 해당 객체의 사용 빈도에 따라 GC의 회수 대상이 되는지가 결정된다는 점이 다르다. 객체가 자주 사용될 수록, 메모리가 여유로울수록 GC의 대상이 되는 빈도가 줄어든다.
메모리의 영향을 받아 GC의 대상이 되므로 weakly reachable 객체와는 다르게 GC 동작 시마다 회수 대상으로 지정되지 않아 객체가 더 긴 생명주기를 가진다. 만약 메모리가 부족하다고 판단될 경우, softly reachable 객체를 우선적으로 회수해서 메모리를 확보하게 된다.
✨ PhantomReference
PhantomReference에 대한 내용은 아직 이해도가 부족하여 간략하게만 적었습니다. 아직은 너무 어려운ㅠ
PhantomReference는 객체가 GC에 의해 실제로 회수되는 상태를 추적하기 위해 사용된다. 객체가 "더 이상 어떤 방식으로도 유효한 참조될 수 없는 상태"가 되는 상태를 감지하는 용도로 설계되었다.
- GC 시 회수 대상이지만, 회수 대상으로 지정되는 것과 실제로 회수가 되는 과정은 연속적으로 이루어지지 않기 때문에, "실제로 회수가 될 때" 특정 작업을 하기 위해 사용된다.
- get() 함수를 호출해서 참조하는 객체를 가져와 사용하는 Weak/Soft Reference 객체들과 다르게, get() 함수를 호출하면 항상 null을 반환한다.
- ReferenceQueue와 함께 사용된다. 객체가 GC의 회수 대상이 될 때, ReferenceQueue에 추가되는 것으로 "객체가 회수되는 중"이라는 이벤트를 감지할 수 있다.
예전에 메모리 관리와 관련한 포스팅을 읽은 적이 있다. Bitmap의 메모리 할당 해제와 관련된 글이였는데,
finalize() 메서드가 "GC에 의해 객체가 회수되는 시점에 특정 로직을 수행하기 위해 사용한다"라고 되어 있었다.
이는 PhantomReference와 동일한 목적이기 때문에, PhantomReference가 finalize 메서드를 대체하기 위해 만들어졌다는 것을 알 수 있다.
그 포스팅에서 finalize 메서드를 오버라이드할 경우에 객체가 회수 대상이 됨에도 즉시 호출되지 않는다는 문제가 있었다고 해서 Object 클래스의 finalize 메서드를 간단하게 살펴보았다.
// A subclass should avoid overriding the finalize method unless the subclass
// embeds non-heap resources that must be cleaned up before the instance is collected.
@Deprecated(since="9")
protected void finalize() throws Throwable { }
finalize 메서드는 Java 9 이후로 Deprecated되었으며, Heap 영역 외부의 리소스를 해제해야하는 경우가 아니라면 오버라이드를 권장하지 않는 것을 볼 수 있다.
그래서 PhantomReference를 만들어서 finalize의 문제를 개선하면서 사용자의 코드가 GC에 좀 더 관여할 수 있도록 개선된걸까?
PhantomReference는 finalize보다 더 예측 가능하고 안전하다는데, 난 둘 다 한 번도 사용해 본 적이 없어서 비교를 해 볼 기회가 없을 것 같다...
3. Reachable 상태

도달 가능성(Reachablility)는 위처럼 5가지로 나누어 진다.
(1) Strongly Reachable
GC Root 요소와 객체 사이에 다른 Reference 객체가 존재하지 않는 상태로 참조되는 상태이다.
(2) Weakly Reachable
GC Root 요소에서 강한 참조나 SoftReference 객체를 통해서 도달할 수 없고, Weak Reference 객체를 통해서만 도달할 수 있는 상태이다.

위 그림에서는 WeakReference와 A 객체 사이에 SoftReference 객체가 존재하기 때문에, 객체 A는 softly reachable 상태로 간주된다.
(3) Softly Reachable
Softly Reachable 객체도 당연히 SoftReference 객체와 객체 사이에 다른 Reference 객체가 존재하지 않을 경우를 가리킨다. 근데 만약 WeakReference 객체와 SoftReference 객체가 같은 객체를 참조한다면, 이 객체는 어떤 상태를 가지게 될까?

정답은 softly reachable 상태인데, 그 이유는 GC가 참조 상태를 관리하기 위한 우선순위에 있다.
✨ GC의 Reachability 순위(강한 순)
- Strongly reachable
- Softly reachable
- Weakly reachable
- Phantomly reachable
- Unreachable
도달 가능성이 높을수록 객체가 더 오래 생존하므로, WeakReference에 의해 참조되고 있다고 해도 더 강한 참조인 SoftReference가 참조하고 있다면 더 긴 생명주기를 가지는 Softly reachable 상태로 간주된다.

만약 객체 A가 PhantomReference에 의해 참조되고 있다고 하더라도, 도달가능성에 따라 SoftReference보다 약한 참조로 간주되므로 A는 Softly reachable 상태가 될 것이다.
하지만 SoftReference에서 객체 A까지 도달하는 참조 체인 중에 Weak/Phantom Reference가 존재하면 객체 A는 Weakly/Phantomly reachable 상태가 된다.
그 대신 Weak/Phantom Reference 객체가 softly reachable 상태가 될텐데, 이 부분은 아직 너무 복잡해서 따로 학습하지 않았다.. 참조 객체를 참조 객체로 감싸는 방법은 아직 사용해 본적이 없다 ㅠㅠ
(4) Phantomly Reachable
객체를 가리키는 참조가 PhatomReference뿐일 때 Phantomly reachable 상태가 되며, 해당 객체는 더 이상 참조가 불가능해 진다.
(5) Unreachable
Gc Root 요소들로부터 시작되는 참조 체인으로 참조되지 않는 상태를 가리킨다. 이 요소들이 Gc Root 요소로 부터 참조되는 객체들을 역으로 참조한다 해도, Unreachable 상태로 간주되어 GC의 메모리 회수 대상이 된다.
❓WeakReference를 사용하면 GC가 돌 때 반드시 메모리에서 해제가 될까?
어느 면접 질문 리스트에서 가져온 질문인데, 이게 오늘 작성한 포스팅의 원래 주제였다.
WeakReference가 참조하는 객체가 weakly reachable 상태가 되면,GC는 약한 참조만 존재하는 객체를 회수 대상으로 인식한다. 그리고 수거 시 해당 객체에 대한 WeakReference의 참조를 null로 설정함으로써 수거가 이루어진다.
하지만 "회수 대상이 된다"가 "반드시 메모리에서 해제가 된다"로 직결되지는 않는다.
GC는 실행 시점과 메모리 해제 시점을 예측할 수 없으며, 내부 알고리즘이나 메모리 사용량에 따라 다음 GC 사이클에서 회수될 수도 있는 "비결정적"인 방식으로 동작하기 때문이다.
이처럼 WeakReference를 사용하면 필요할 경우 접근이 가능하면서, 객체를 GC의 회수 대상으로 만들 수는 있지만 GC 시에 즉각적인 메모리 회수는 보장되지 않는다.
✨ 번외) System.gc() 메서드 주석의 표현이 재밌다
우리가 System.gc() 를 호출해서 GC를 요청할 수는 있지만, 실제로 호출 시점과 GC 실행 시점은 예측할 수 없는 것도 GC의 비결정적인 특성 때문이다.
시스템의 모든 상황을 고려하지 못하는 우리가 "이걸 지금 GC 해야 해"라고 판단하는 것보다, 시스템이 자체적으로 판단하는게 더 효율적이기 때문이 아닐까라는 생각을 해 본다.
/*
* Runs the garbage collector.
* Calling the gc method suggests that the Java Virtual Machine expend effort toward
* recycling unused objects in order to make the memory they currently occupy available
* for quick reuse. When control returns from the method call, the Java Virtual Machine
* has made a best effort to reclaim space from all discarded objects.
*
* 해석해 보면,
* 가비지 콜렉터를 실행합니다.
* gc 메서드를 호출하면 Java Virtual Machine이 현재 차지하고 있는 메모리를
* 빠르게 재사용할 수 있도록 사용되지 않는 객체를 재활용하는 데
* 노력을 기울인다는 것을 의미합니다. 메서드 호출에서 제어가 반환되면
* Java Virtual Machine은 모든 폐기된 객체에서 공간을 회수하기 위해 최선을 다했습니다.
*/
public static void gc() {
int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion();
if (targetSdkVersion > 34) {
Runtime.getRuntime().gc();
} else {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = targetSdkVersion <= 34 ? justRanFinalization : true;
if (shouldRunGC) {
justRanFinalization = false;
} else {
runGC = true;
}
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
}
}
gc() 메서드의 주석에도 "최선을 다한다"는 표현을 사용한 이유는 결정적으로 동작하지 않아서이지 않을까!
위트 있는 주석이다.
'개발 > Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 가변성을 제한하라 (0) | 2025.04.09 |
---|---|
[Kotlin] sealed class, enum class 바이트 코드 뜯어보기 (1) | 2025.03.11 |
[Kotlin] 예외 처리 - 예상한 예상 밖의 결과 (0) | 2025.02.19 |
[이펙티브 코틀린] 프로퍼티는 동작이 아니라 상태를 나타내야 한다 (0) | 2025.02.07 |
[Kotlin] 타입 소거와 Star projection (0) | 2024.06.27 |