Reading/이펙티브 자바

아이템 7. 다 쓴 객체 참조를 해제하라

going.yoon 2022. 4. 24. 16:56

스택을 구현한 아래의 코드를 살펴보자.

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * 원소를 위한 공간을 적어도 하나 이상 확보한다.
     * 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }


    public static void main(String[] args) {
        Stack stack = new Stack();
        for (String arg : args)
            stack.push(arg);

        while (true)
            System.err.println(stack.pop());
    }
 }

위와 같은 코드에서는 GC가 동작하지 않아 메모리 누수가 발생할 수 있다. 스택에서 꺼내진 객체들을 더이상 프로그램에서 사용하지 않더라고 스택이 그 객체들의 다 쓴 참조(obsolete reference)를 가지고 있기 때문이다. 이러한 상황에서는 pop 부분에서 객체가 사용이 완료되었으면 GC가 해당 참조를 해제할 수 있도록 프로그램에서 null 처리를 해주면 된다. 이렇듯 자기의 메모리를 직접 관리하는 클래스라면 항시 메모리 누수에 주의해야 한다.

    // 코드 7-2 제대로 구현한 pop 메서드 (37쪽)
    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제
        return result;
    }

 

메모리 누수를 일으키는 주범인 캐시를 처리하기 위해서는 WeakHashMap이나 Scheduled ThreadPoolExecutor를 활용하여 엔트리를 청소해주는 것이 좋다. 또한 리스너와 콜백의 문제에서 클라이언트가 콜백을 등록만 하고 해지하지 않을 경우에 가비지 컬렉터가 수거해갈 수 있도록 콜백을 weak reference로 설정해주는 것이 좋다. 메모리 누수는 힙 프로파일러와 같은 디버그 도구를 사용해서 발견될 수 있따.