다 쓴 객체 참조를 해제하라

김종준·2023년 3월 28일
0

이펙티브자바

목록 보기
6/63

다 쓴 객체 참조를 해제하라

// 자바 GC 공부한 이후에 다시 공부하기

스택

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);
        }
    }
}

보통의 스택 코드이다.

위의 코드에서 스택이 커졌다 줄어들었을 때 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않는다.

왜냐하면 스택이 그 객체들의 다 쓴 참조를 여전히 가지고 있기 때문이다.

그렇기에 메모리 누수가 일어날 수 있다고 한다.

그렇다면 메모리 누수를 막을 방법은 무엇일까?

해당 참조를 다 썼을 때 null 처리(참조 해제)하면 된다.

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

다 쓴 참조를 null 처리하면 다른 이점도 따라오는데 실수로 null 처리한 참조를 사용할 때 프로그램이 NullPointerException을 던지며 종료된다.

하지만 이러한 처리를 일일이 모든 객체에 할 필요는 없고 이를 적용할 경우는 예외적인 경우여야 한다.

다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다.

그렇다면 null 처리는 언제 해야 하고 위의 Stack 클래스는 왜 메모리 누수에 취약한 걸까?

스택이 자기 메모리를 직접 관리하기 때문이다.

위 스택은 elements 배열로 저장소 풀을 만들어 원소를 관리한다.

배열의 활성 영역에 속한 원소들이 사용되고 비활성 영역은 쓰이지 않는다.

하지만 가비지 컬렉터는 이 사실을 알 수가 없다.

그렇기에 프로그래머가 비활성 영역이 되는 순간 null 처리를 해서 해당 객체를 더는 쓰지 않을 것임을 알려야 한다.

캐시

캐시 역시 메모리 누수를 일으키는 주범이다.

객체 참조를 캐시에 넣고 나서, 객체를 다 쓴 뒤로도 한참을 그냥 놔두는 일을 자주 접할 수 있다.

운 좋게 캐시 외부에서 키를 참조하는 동안만(값이 아닌!) 엔트리가 살아 있는 캐시가 필요한 상황이라면 WeakHashMap을 사용해 캐시를 만들면 다 쓴 엔트리는 즉시 자동으로 제거될 것이다.

캐시를 만들 때 보통은 캐시 엔트리의 유효 기간을 정확히 정의하기 어렵기 때문에 시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 흔히 사용한다.

이러한 경우에는 Scheduled ThreadPoolExecutor 같은 백그라운 스레드를 활용하거나 캐시를 LinkedHashMap을 사용하여 구현해 removeEldestEntry 메서드를 사용해 새 엔트리를 추가할 때 부수 작업으로 스레드를 청소하는 방법이 있다.

리스너, 콜백

클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면, 뭔가 조치하지 않는 한 콜백은 계속 쌓여간다.

이럴 때 콜백을 약한 참조로 저장하면 가비지 컬랙터가 즉시 수거한다.

예를 들어 WeakHashMap에 키로 저장하면 된다.

1개의 댓글

comment-user-thumbnail
2023년 3월 28일

오.. 하루에 포스팅 두 개..

답글 달기