Item 7. 다 쓴 객체 참조를 해제하라

심규환·2022년 1월 15일
0

Effective Java

목록 보기
7/29
post-thumbnail

Java에서는 메모리를 알아서 관리해주는 GC(garbage collection)이 존재하기 때문에 사용자가 메모리 관리의 부담을 덜어주게 된다.
하지만 GC가 모든 메모리를 관리하지는 못한다. 자신이 직접 메모리를 관리하는 클래스의 경우 GC의 관리에서 벗어나게 되기 때문에 클래스 내부에서 메모리 누수를 방지하기 위해 반드시 예방을 해야한다.

밑에는 간단한 메모리 누수가 이루어지는 Stack 클래스이다.

public class Stack{
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
    public Stack(){
    	return elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    
    pulbic void push(Object e){
    	ensureCapacity();
    	elemetes[size++] = e;
    }
    
    public Obejct pop(){
    	if( size == 0 )
        	throw new EmptyStackException();
        return elementes[--size];
    }
    
    private void ensureCapacity(){
    // 공간을 두 배로 늘린 후, 복붙
    	if(elements.length == size)
        	elements = Array.copyOf(elements, 2 * size +1);
    }

}

얼핏 보기에는 문제가 없어보인다. 하지만 잘 살펴보면 메모리 누수의 위험성이 보인다. 그러면 어디서 메모리 누수가 일어날까?
바로 pop()을 할 때이다. 위의 코드를 보면 pop()을 하고 단순히 size를 줄여주는 작업만 한다. size 안쪽의 데이터들은 사용하려는 영역은 '활성 영역'이라 한다. 이 활성 영역 밖으로 벗어난 데이터 다 쓴 참조(obsolete reference)라 하는데. 다 쓴 참조는 아직 존재하기 때문에 반드시 null 처리를 해야한다.

pop() 수정

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

이런 다 쓴 객체를 남겨놓고 실수로 접근하게 된다면 NullPointerException을 던지며 프로그램은 즉시 종료된다.
그러면 모든 객체를 다 쓰자마자 null 처리를 해줘야할까? 그렇지 않다. 그렇게 하나 하나 null 처리를 하게 한다면 코드가 지저분해진다.
가능하면 유효 범위(scope) 밖으로 밀어내서 아예 참조조차 못하게 하는 것이 좋다.
위와 같이 메모리를 직접 관리하는 클래스는 GC에 관리에서 벗어나기 때문에 항시 메모리 누수에 주의해야한다.

또 메모리 누수가 이루어지는 것 중에 하나는 캐시이다. 객체 참조를 캐시에 넣고 까먹는 다면 역시나 메모리 누수의 발화점을 그대로 두고 있는 것과 다름없다.

이를 위한 대응책으로는 WeakHashMap을 사용해서 캐시를 만드는 것이다.
약한 참조(Weak Reference)로 만들어서 GC의 영역안에 두는 것이다.

세 번째는 리스너(Listener)콜백(Callback)이다. 클라이언트가 콜백을 등록해 놓고 명확히 해지하지 않는다며 콜백은 계속 쌓이게 된다. 이럴 때 콜백에 약한 참조로 저장하면 GC가 즉시 수거해간다.

profile
장생농씬가?

0개의 댓글