본문 바로가기

IT/자바

[Effective Java] item 7 다 쓴 객체 참조를 해제하라

반응형

자바가 가비지 컬렉터를 갖춘 언어라고 해서 메모리 관리는 무시해서는 안된다.

 

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 o){
        ensureCapacity();
        elements[size++] = o;
    } 
    
    public Object pop(){
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }
    
    /*
    원소를 위한 공간을 적어도 1개 이상 확보, 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다.
     */
    private void ensureCapacity(){
        if (elements.length == size){
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

이 코드에서 메모리 누수가 발생하는 부분은 어디일까 ??

 

 

답은 pop() 메소드이다.

스택에서 꺼낸 객체들을 가비지 컬랙터가 회수하지 않기 때문이다.

(객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체가 참조하는 모든 객체를 회수해가지 못함)

 

메모리 누수를 막기 위한 해결책으론 해당 참조 객체를 다 사용한 경우 null 처리를 해주면 된다.

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

하지만 null 처리에 집착할 경우 코드가 지저분해질 수 있다.

 

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

public static void main(String[] args) {

        try{
            stack s = new stack();

            s.push("temp1");
            s.push("temp2");
            s.push("temp3");


            Object a = s.pop();
            Object b = s.pop();

        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }

스택의 경우 왜 메모리 관리에 취약한다.

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

 

스택은 배열의 활성화 영역에 속한 객체만을 사용하고 비활성화 영역은 사용하지 않는데 가비지 컬렉터는 이 사실을 알 수가 없다.

그러므로 null 처리를 통해 가비지 컬렉터에게 이 객체를 사용하지 않는다는 걸 알려야 한다.

 

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

 

캐시 역시 자기 메모리를 직접 사용하기에 메모리 누수가 발생할 수 있다.

이 경우 만약 키를 참조하는 동안에만 살아있으면 되는 캐시라면 WeakHashMap을 사용하는 걸 권장한다.

더보기

WeakHashMap = 약한 참조 해쉬맵

 

"내가 D라는 데이터를 사용하는 동안 누가 D를 필요로 한다면 알려줘 같이 쓰자.
하지만 내가 필요없다면 그건 버리고 다음에 필요할 때 다시 생성할꺼야"

 

이 경우 메모리 누수를 막는 방법은 두가지가 있다.

1) ScheduledThreadPoolEcecutor와 같이 백그라운드 스레드 사용

2) 캐시에 새 엔트리를 추가할 때 부수 작업으로 수행하는 방법

ex) LinkedHashMap 인 경우 removeEldestEntry 메서드를 써서 null 처리를 할 수 있다.

 

리스너(=콜백) 역시 메모리 누수가 발생할 수 있다.

클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면 콜백이 계속 쌓일 수 있기 때문이다.

 

해결 방법으로 콜백을 약한 참조에 저장하는 것이다.

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

 

[WeakHashMap 참고 블로그 링크]

 

 

 

반응형