본문 바로가기
책 정리/이펙티브 자바

[이펙티브 자바] 다 쓴 객체 참조를 해제하라 - item 7

by chanwoodev 2023. 6. 21.

 

메모리 누수 발생

 

객체 참조가 존재한다면 GC(가비지 컬렉터)가 객체 회수하지 못함.

 

예시) 

public class Stack {
    private Object[] elements;
    private int size = 0;

    ...
    
    //메모리 누수 발생
    public Object pop() {
    	if (size == 0) 
        	throw new EmptyStackException();
        return elemets[--size];
    }

}

위 상황에서 element의 원소가 계속 객체를 참조하고 있기 때문에 GC가 회수하지못함

-> 메모리 누수 발생

 

메모리 누수가 발생한다면 메모리 사용량 증가 -> 성능 저하 유발

심한경우 디스크 페이징, OutOfMemoryError이 발생할 수 있음

 

참조 해제 (null 처리)로 해결

public Object pop() {
    if (size == 0) 
        throw new EmptyStackException();
    Object result = elements[--size];
    
    // 참조 해제
    elements[size] = null;
    
    return result;
}

 

위 Stack에서 메모리 누수가 발생한 이유는 자기 메모리직접 관리하기 때문임

프로그래머가 직접 null 처리를 통해 가비지 컬렉터에게 알려주어야 함

 

객체 참조를 Null로 처리하는 일은 예외적인 경우여야 함.

참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내라

  • 변수의 범위를 최소가 되게 정의해야함 (자동으로 변수가 할당해제가 될 수 있도록)

 

캐시의 메모리 누수

 

객체 참조를 캐시에 넣고 해당 객체를 다 쓰고도 놔두면 GC가 객체를 회수할 수 없게 됨 (참조하고 있기 때문에)

 

해결책

1. WeakHashMap 사용해 자동 할당 해제 (weak reference가 사용됨)

weak reference는 해당 객체 참조가 없다면 자동으로 GC가 회수함

 

참고: WeakHashMap - baeldung

 

2. ScheduledThreadPoolExecutor 등으로 백그라운드 스레드 활용하여 엔트리를 청소한다

 

3. LinkedHashMap의  removeEldestEntry를 overriding 하기

 

실제 구현부 코드를 살펴봅시다.

...

// linked hashmap 구현부 일부
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }


...
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

 

위의 removeEldestEntry를 map의 size와 연관해서 오버라이딩한다면 일정 크기가 넘어갈 때 자동으로 오래된 객체를 할당 해제할 수 있습니다.

 

예시)

public class MyLinkedHashMap<K, V> extends LinkedHashMap<K, V> {

    private static final int MAX_ENTRIES = 5;

    public MyLinkedHashMap(
      int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }

}

참고: LikedHashMap - baeldung

리스너, 콜백의 메모리 누수

 

클라이언트가 콜백을 등록만 하고 명확한 해지를 하지않으면 콜백은 계속 쌓여갈 수 있다.

weak reference로 콜백을 저장하면 GC가 즉시 수거해가도록 할 수 있다.