본문 바로가기
Java

Effective Java #06 유효기간이 지난 객체 참조는 폐기하라

by NaHyungMin 2016. 4. 13.

C나 C++ 처럼 손수 메모리를 관리 해야 하는 언어를 쓰다 가비지 컬렉터가 있는 언어를 사용하게 되면 프로그래밍이 아주 쉬워진다.

하지만 필자 생각엔 어떤 언어든 메모리 관리가 제일 중요하다고 생각한다.


자바나 C#에서 가장 큰 고민은 얼마나 효율적으로 코드를 구현할 것인가 인 것 같다.

이 이야기는 재사용해야 하는 객체는 재사용하고 폐기하여 반환해야 하는 객체는 반환해야 한다는 것이다.

초급 개발자와 중급, 고급 개발자에 차이점도 이 부분에서 나타나는 것 같다.


아래의 간단한 스택(stack) 구현 사례를 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 메모리 누수(memory leak)가 어디서 생기는지 보이는가?
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);
        }
    }
}
cs

코드 자체로는 아무 문제가 없다.
하지만 이 코드는 메모리 누수(memory leak) 문제가 있다.
가비지컬렉터의 경우 해당 객체에 참조되는 다른 객체들도 메모리 수집에 제외된다.

다음은 수정된 코드이다.

1
2
3
4
5
6
7
8
9
10
public Object pop() {
    if(size == 0) {
        throw new EmptyStackException();
    }
        
    Object result = elements[--size];
    elements[size] = null//참조 제거
 
    return result;
}
cs

이 같은 코드를 구현하다보면 모든 객체에 null 처리하는 경우가 생길 수도 있는데, 꼭 그럴 필요는 없다.
참조 객체와 다르게 일반 객체는 생명주기를 생각하여 작성하면 된다.
캐시(cache)도 메모리 누수가 흔히 발생하는 장소이다.
해결책은 다음과 같다.

1. WeakHashMap을 가지고 캐시를 구현(이 경우 외부에서 참조가 완료되면 자동으로 키-값이 삭제 외부 참조의 따라 수명이 결정되는 상황에만 적용)
2. 일반적인 경우 후면 스레드(background thread) 등을 사용하여 작업(LinkedHashMap 클래스를 사용하면 접근법을 구현하기 좋다고 한다 removeEldestEntry 함수가 제공되기 때문 좀 더 복잡한 경우 java.lang.ref를 직저접 사용해야 할 수도 있다.)

메모리 누수가 흔히 발견되는 또 한 곳은 리스터(listener) 등의 역호출자(callback)이다.
API를 사용하는 클라이언트가 역호출자를 명시적으로 제거하지 않을 경우, 적절한 조치를 취하기 전까지 메모리는 점유된 상태로 남아있게 된다.
이 경우 역호출자에 대한 약한 참조(weak reference) 나 이보다 조금 강한 참조인 WeakReference를 사용하는 것이 좋다.