Closed zpqmdh closed 3 months ago
약한 참조로 저장하면 GC가 즉시 수거해간다고 하는데 콜백을 WeakHashMap 으로 저장한다는게 와닿지 않아서 그런 데 간단한 예시를 들어주실 수 있나요?
WeakHashMap에 대해 짧게 먼저 설명드리겠습니다. WeakHashMap
은 Map
인터페이스를 구현한 컬렉션으로 Map
과 동일하게 Key-Value로 데이터를 관리합니다.
차이점은 WeakHashMap
에 저장된 Key는 Weak Reference를 가진다는 것입니다.
따라서 Key 객체가 더 이상 사용되지 않는다면 (또는 Key 객체가 다른 객체에게 참조되지 않는 상황이라면) GC가 자동으로 해당 엔트리를 삭제하고 자원을 회수해 가는 현상이 일어날 수 있습니다.
WeakHashMap - Java 8 SE document
다음과 같은 예시를 봅시다.
public class CallBackTest {
@FunctionalInterface
public interface Callback {
public void method();
}
public static void main(String[] args) {
Object obj = new Object();
Map<Object, Callback> weakHashMap = new WeakHashMap<>();
CallBackTest.Callback callback = new CallBackTest.Callback() {
@Override
public void method() {
// do something...
}
}
// obj를 key로 하여 WeakedHashMap에 저장합니다.
// 이때 Key가 되는 obj는 약한 참조로 설정되어 저장됩니다.
weakHashMap.put(obj, callback);
obj = null;
// GC에 의해 콜백 함수가 해당 Entry를 회수할 때 까지 대기합니다.
while(true) {
System.gc();
if(weakHashMap.size() == 0) {
break;
}
}
}
}
콜백 함수를 만든 뒤 해당 콜백 함수를 Key인 obj
와 함께 WeakHashMap
에 저장합니다.
이때, Key인 obj
가 null
이 되는 상황을 봅시다. obj
는 더 이상 사용되지 않습니다.
기본적인 HashMap
의 상황이라면 obj
가 null
이 되어도 해당 Key를 가지는 엔트리는 삭제되지 않습니다. 따라서 클라이언트가 Key인 obj
를 null
로 할당해도, callback
은 여전히 엔트리에 계속 남아 메모리를 점유하게 됩니다. (Memory Leak)
반면 WeakHashMap
에서 obj
가 null
이 되면, GC가 Key인 obj
를 더 이상 사용하지 않는 객체로 보고 해당 엔트리를 삭제 후 회수합니다. 엔트리를 삭제하면서 자동으로 엔트리의 Value 값인 callback
도 캐시에서 함께 제거됩니다. 따라서 캐시에서 메모리 누수가 발생하지 않습니다.
해당 방식을 사용하면 클라이언트 측(Calle) 에서 key를 null
로 세팅해주기만 해도 되므로 편리하게 관리가 가능합니다. 다만 WeakHashMap
을 사용한 캐시의 경우, 이미 참조가 되어 있어도 삭제가 될 수 있으므로 이에 주의가 필요합니다.
약한 참조(WeakReference
@FunctionalInterface
interface Callback {
void callbackMethod();
}
class Callee {
private WeakReference<Callback> callback;
public void setCallback(Callback callback) {
this.callback = new WeakReference<>(callback);
}
private callbackConditional() {
// 콜백 메서드 호출이 필요한 상황에 호출
callbackMethod();
}
}
class Caller {
private Callee callee;
private Callback callback = () -> {
System.out.println("callback method called");
};
public Caller() {
callee.setCallback(callback);
}
}
해당 코드는 Callee 에서 Callback 객체를 저장할 때 WeakReference로 Wrap하여 할당하고 있습니다.
해당 방식을 사용하면 이후 Caller가 사라질 때(null이 되거나 참조되지 않아 회수될 때) Callback에 대한 참조가 Callee의 WeakReference만 존재하게 됩니다.
GC는 어떤 객체에 대한 Strong Reference가 없을 경우, Weak Reference가 남아 있어도 해당 객체의 자원을 회수합니다. 따라서 Callback에 대한 참조가 Callee의 Weak Reference만 남기 때문에 콜백 함수가 자연스럽게 GC에 의해 회수되게 됩니다.
참조 유형에 대해 이번에 알게 되었는데, 들어주신 예시 덕분에 각 유형의 차이를 더 쉽게 이해할 수 있게 되었어요!! 감사합니다 :)
자바에는 세 가지 참조 유형이 있다고 합니다.
출처: https://blog.breakingthat.com/2018/08/26/java-collection-map-weakhashmap/
아이템 7 39페이지 내용에 따르면, 메모리 누수의 세 번째 주범은 콜백을 등록만 하고 명확히 해지하지 않았을 때입니다. 이때, 약한 참조로 저장하면 GC가 즉시 수거해간다고 하는데 콜백을 WeakHashMap 으로 저장한다는게 와닿지 않아서 그런데 간단한 예시를 들어주실 수 있나요?