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

초코칩·2024년 3월 26일
0

Java

목록 보기
11/14
post-thumbnail

자바에서는 GC를 통해 메모리 관리에 유연해진다.

메모리 누수

아래 코드는 별 문제가 없어 보인다.

public class Stack {

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

하지만 스택을 사용하는 프로그램을 오래 실행하다보면 점차 GC 활동과 메모리 사용량이 늘어나 성능이 저하될 것이다. 상대적으로 드문 경우긴 하지만 디스크 페이징이나 OOM을 일으켜 프로그램이 예기치 않게 종료되기도 한다.

pop() 메서드는 스택에서 꺼내진 객체들을 GC가 회수하지 않는다. 객체의 참조를 배열이 가지고 있기 때문이다.

참조

GC 언어에서는 메모리 누수를 찾기가 까다롭다. 객체 참조 하나를 살려두면 GC는 그 객체뿐 아니라 그 객체가 참조하는 모든 객체를 회수하지 못한다.

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

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

그렇다고 해서 모든 객체를 다 쓰자마자 일일이 null 처리하는 것은 코드를 지저분하게 한다. 객체 참조를 null 처리하는 것은 예외적인 경우여야 한다.

캐시

객체 참조를 캐시에 넣고, 이를 잊은 채 그 객체를 다 쓴 뒤로 놔두는 일을 접할 수 있다.

외부에서 참조할 경우

외부에서 키를 참조하는 동안만 엔트리가 살아 있는 상황이라면 WeakHashMap을 사용해 캐시를 만들 수 있다.

캐시 정리

캐시를 만들 때 엔트리의 TTL을 정하는 것이 가장 어렵다. 따라서 시간이 지날수록 엔트리의 가치를 떨어트리는 방식을 사용한다. 이런 방식에서는 엔트리를 정리해야 한다. 백그라운드 스레드를 이용하거나 캐시에 새 엔트리를 추가할 때 부수 작업으로 수행하는 방법이 있다.

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

리스너 혹은 콜백

클라이언트가 콜백을 등록만 하고 명확히 해제하지 않는다면, 콜백이 계속 쌓일 수 있다. 이럴 때 콜백을 약한 참조로 저장하면 GC가 즉시 수거해간다.

정리

메모리 누수는 겉으로 드러나지 않는다. 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다. 문제의 예방법을 익혀두자.

profile
초코칩처럼 달콤한 코드를 짜자

0개의 댓글