■ 메모리 누수가 일어나는 예시 코드
자바언어처럼 가비지 컬렉터를 갖춘 언어로 넘어오면 다 쓴 객체를 알아서 회수해가니 메모리 관리를 더이상 신경쓰지 않는다고 착각할 수 있다.
다음 스택 코드를 살펴보면 스택에서 꺼내진 객체들은 가비지 컬렉터가 회수하지 않는다. 프로그램에서 그 객체들을 더이상 사용하지 않더라도 말이다. 이 스택이 다 쓴 참조를 여전히 가지고 있기 때문이다. 여기서 다 쓴 참조란 문자 그대로 앞으로 다시 사용하지 않을 참조를 뜻한다.
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Object[] getElements() {
return 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);
}
}
}
가비지 컬렉션 언어에서는 의도치 않게 객체를 살려두는 메모리 누수를 찾기가 아주 까다롭다. 객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐만 아니라 그 객체가 참조하는 모든 객체(그리고 또 그 객체들이 참조하는 모든 객체)를 회수하지 못한다. 그래서 단 몇개의 객체가 매우 많은 객체를 회수하지 못하게 할 수 있다.
■ 메모리 누수 해결
메모리 누수의 해결 방법은 다 사용한 참조의 경우 null(참조 해제) 처리를 하면 된다. 예시의 스택 클래스에서는 각 원소를 pop 했을 경우 참조가 더 이상 필요 없어지기 때문에 null처리를 해준다. 또한 null처리를 해주면 null 처리한 참조를 실수로 사용하려고 하면 프로그램은 NullPointerException을 던지며 종료시켜준다.
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
■ null처리를 해야하는 경우
이 문제로 크게 데인 적이있는 프로그래머는 모든 객체를 다 사용하자마자 null처리를 하는데 혈안이 되는데 그럴 필요도 없고 바람직하지도 않다. 프로그램이 필요 이상으로 지저분해질 수 있다.
일반적으로 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야한다. Statck 클래스는 객체 자체가 아니라 객체 참조를 담는다. 배열의 활성 영역에 속한 원소들이 사용되고 비활성 영역은 쓰이지 않는다. 문제는 가비지 컬렉터에서 이 사실을 알길이 없다는 것이다. 그러므로 프로그래머는 비활성 영역이 되는 순간 null처리해서 해당 객체를 더이상 사용하지 않는다는 것을 가비지 컬렉터에 알려야한다.
■ 캐시
캐시 역시 메모리 누수를 일으키는 주범이다. 객체 참조를 캐시에 넣어 두고 나서 그 객체를 다 쓴 상태로 한참을 그냥 놔두는 일을 발견할 수 있다. 키를 참조하는 동안만 엔트리가 살아있는 캐시가 필요한 상황이라면 WeahHashMap을 사용해서 캐시를 만들자. 다 쓴 엔트리는 그 즉시 자동으로 제거될 것 이다.
■ 리스너 또는 콜백
리스너와 콜백또한 메모리 누수의 주범이다. 클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면 콜백은 계속 쌓여간다. 이럴 때 콜백을 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다. 예를 들어 WeakHashMap에 키로 저장하면 된다.
REFERENCE
- 조슈아 블로크, Effective Java
'Effective Java' 카테고리의 다른 글
[Effective Java] 아이템10 equals는 일반 규약을 지켜 재정의하라 (0) | 2021.01.22 |
---|---|
[Effective Java] 아이템8 finalizer와 cleaner 사용을 피하라 (0) | 2021.01.21 |
[Effective Java] 아이템6 불필요한 객체 생성을 피하라 (0) | 2021.01.16 |
[Effective Java] 아이템5 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2021.01.14 |
[Effective Java] 아이템4 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2021.01.13 |