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

Effective Java 3e 아이템 7를 요약한 내용 입니다.

자바처럼 가비지 컬렉터를 갖춘 언어로 넘어오면 프로그래머의 삶이 훨씬 평안해진다.

그래서 자칫 메모리 관리에 더 이상 신경 쓰지 않아도 된다고 오해할 수 있는데 절대 사실이 아니다

    public Class Stack {

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

    }

위의 코드는 기본적으로 많이 사용하는 스택 클래스이다.

그런데 이 Stack 클래스를 오래 실행하다 보면 점차 가비지 컬렉션 활동메모리 사용량이 늘어나 결국 성능이 저하될 것이다.

이 코드에서는 스택이 커졌다가 줄어들었을 때 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않는다. 여기서 다 쓴 참조란 문자 그대로 앞으로 다시 쓰지 않을 참조를 뜻한다. 앞의 코드에서는 elements 배열의 '활성 영역'밖의 참조들이 모두 여기에 해당한다.

객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐 아니라 그 객체가 참조하는 모든 객체(그리고 또 그 객체들이 참조하는 모든 객체)를 회수해가지 못한다.

JCF 에서 관리되는 리스트의 필드는 수동으로 메모리를 회수해야 할까?

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

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

다 쓴 참조를 null 처리하면 다른 이점도 따라온다. 만약 null 처리한 참조를 실수로 사용하려 하면 프로그램은 즉시 NullPointerException을 던지며 종료 된다.

하지만 모든 데이터를 사용한 이후에 null 처리할 필요는 없다. 이는 바람직하지도 않고 코드를 지저분하게 만들 뿐이다.

객체 참조를 null 처리하는 일은 예외적인 경우여야 한다. 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scope)밖으로 밀어내는 것이다.

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

바로 스택이 자기 메모리를 직접 관리하기 때문이다. 이 스택은 (객체 자체가 아니라 객체 참조를 담는) elements 배열로 저장소 풀을 만들어 원소들을 관리한다.

비활성 영역의 객체가 더 이상 쓸모없다는 건 프로그래머만 아는 사실이다. 그러므로 프로그래머는 비활성 영역이 되는 순간 null 처리해서 해당 객체를 더는 쓰지 않을 것임을 가비지 컬렉터에 알려야 한다.

일반적으로 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항싱 메모리 누수에 주의해야 한다.

캐시 역시 메모리 누수를 일으키는 주범이다. 운 좋게 캐시 외부에서 키(Key)를 참조하는 동안만(값이 아니다) 엔트리가 살아 있는 캐시가 필요한 상황이라면 WeakHashMap을 사용해 캐시를 만들자

WeakHashMap는 언제 사용할 수 있을까?

LinkedHashMap은 removeEldestEntry 메서드를 써서 후자의 방식으로 처리한다.

메모리 누수의 세 번째 주범은 바로 리스너 혹은 콜백이라 부르는 것이다. 클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면 뭔가 조치해주지 않는 한 콜백은 계속 쌓여갈 것이다. 이 또한 WeakHashMap에 키로 저장하면 해결 가능 하다.

콜백을 등록만 하고 명확히 해지 하지 않는 케이스는 어떤 케이스 일까?

정리

메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다. 이런 누수는 철저한 코드 리뷰힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다. 그래서 이런 종류의 문제는 예방법을 익혀두는 것이 매우 중요하다.

Last updated