자바에서는 가비지 컬렉터가 다쓴 객체를 알아서 회수해간다 하지만 그렇다고해서 메모리 관리에 신경쓰지 않으면 안된다. 메모리 누수가 발생하는 프로그램을 오래 실행하다보면 점차 가비지 컬렉션 활동과 메모리 사용량이 늘어나 결국 성능이 저하되거나 메모리초과(OufOfMemory
) 오류가 발생할 수 있다. 그러므로 메모리 누수를 주의해야 한다.
메모리 누수를 방지하는 방법은 간단하다. 해당 참조를 다 사용했을 때 null
처리(참조 해제)하면 된다.
[메모리 누수를 방지하는 스택 pop메서드]
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
다 쓴 참조를 null
처리하면 따라오는 부수적인 이점이 있다. 만약 null
처리한 참조를 사용하려하면 NullpointerException
이 발생하며 종료된다.
사실 모든 객체를 다 쓰자마자 null
처리하는 것은 별로 바람직하지 않다. 객체 참조를 null
처리하는 일은 예외적인 경우여야 한다.
다 쓴 참조를 해제하는 가장 좋은 방법
그 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다.
참조를 변수 유효범위 밖으로 밀어내는 일은 변수의 범위를 최소로 정의했다면 자연스럽게 이루어진다.
Stack
클래스가 메모리 누수에 취약한 이유는 자기 메모리를 직접 관리하기 때문이다.
일반적으로 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야하고 원소를 다 사용하면 참조한 객체를 다 null 처리해줘야 한다.
위에서 예를 들었던 스택은 객체 자체가 아니라 객체 참조를 담는 elements
배열로 메모리를 관리한다. 문제는 배열의 비활성 영역은 쓰이지 않는다는 것이고 가비지 컬렉터는 이 사실을 알지 못한다.
그러므로 프로그래머는 비활성 영역이 되는 순간 null
처리를 통해 해당 객체가 더 이상 쓰이지 않는다는 사실을 가비지 컬렉터에게 알려주어야 한다.
WeakHashMap
을 사용해 캐시 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시를 사용ScheduledThreadPoolExecutor
같은 백그라운드 스레드를 활용해 엔트리를 청소LinkedHashMap
의 removeEldesEntry
메서드를 활용해 엔트리를 청소java.lang.ref
패키지를 이용해 더 복잡한 캐시를 만들기리스터 / 콜백
클라이언트가 콜백을 등록만하고 명확히 해지하지 않는다면, 콜백은 계속 쌓이게 된다. 이럴 때 콜백을 약한 참조로 저장하면 가비지 컬렉터 가 즉시 수거해 간다.
메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다. 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다. 그래서 이런 종류의 문제는 예방법을 익혀두는 것이 중요하다.