본문 바로가기
책읽고 정리/Effective Java

[Effective Java 3/E] 아이템7. 다 쓴 객체 참조를 해제하라

by jeonghaemin 2021. 6. 22.
728x90

가비지 컬렉터가 다 쓴 객체를 알아서 회수해간다고 메모리 관리에 신경 쓰지 않아도 된다는 것은 아니다.

메모리 누수의 예

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 e) {
        ensureCapacity();
        elements[size++] = e;
    }

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

    /**
    * 원소를 위한 공간을 적어도 하나 이상 확보한다.
    * 배열 크기를 늘려야 할 때마다 두 배씩 늘린다.
    */
    private void ensureCapacity() {
        if(elements.length == size)
            elements = Arrays.copyOf(elements, 2*size + 1);
    }
}

이 스택을 사용하는 프로그램은 메모리 누수가 발생해 결국 성능이 저하된다.

메모리 누수의 원인

스택에서 pop 된 객체들을 가비지 컬렉터가 회수하지 않는다.

스택의 elements 배열의 활성 영역 밖의 다 쓴 참조를 가지고 있기 때문이다.

  • 활성 영역 : 인덱스가 size보다 작은 원소들
  • 다 쓴 참조 : 다시 쓰지 않을 참조

메모리 누수 해결 방법

참조를 다 썼을 때(스택에서 꺼내질 때) Null 처리(참조 회수)를 하면 된다.

null 처리된 참조를 사용하려고 하면, NullPointerException이 발생하는 이점도 있다.

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

객체 참조를 null 처리하는 일은 예외적인 경우여야 한다.

메모리 누수를 일으키는 주범

자기 메모리를 직접 관리하는 클래스

앞선 예제 스택은 객체의 참조를 담고 있는 element 배열로 저장소 풀을 만들어 원소들을 관리하는 클래스이다.

가비지 컬렉터 입장에서는 비활성 영역에서 참조하는 객체도 똑같이 유효한 객체이다.(비활성 영역의 객체가 쓸모없다는 건 개발자만이 알고 있다)

해결 방법

원소를 다 사용한 즉시 그 원소가 참조한 객체들을 다 null처리를 해줘야 한다.

캐시

객체 참조를 캐시에 넣고, 다 쓴 뒤로도 그냥 놔두는 경우

해결 방법

캐시 외부에서 키(key)를 참조하는 동안만 엔트리가 필요한 상황이라면 WeakHashMap을 사용해 캐시를 만들자

  • 키 값이 더 이상 사용되지 않는다고 판단되면 다음 GC때 해당 key, value 쌍을 제거한다.

시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식

리스너(Listener) 혹은 콜백(callback)

클라이언트가 콜백을 등록하고 명확하게 해지하지 않으면, 콜백은 계속 쌓인다.

해결 방법

콜백을 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다. -> 예를 들어 WeakHashMap에 키로 저장

참고

댓글