C,C++ 와는 다르게 자바에서는 GC(가비지 컬렉터)을 통해 메모리를 회수해 가지만. 그렇다고 메모리 관리를 하지 않아도 된다고 오해해서는 절대 안된다
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push() {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size];
}
// 공간이 부족할 시 2배로 공간을 확장한다.
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
간단한 스택을 구현한 코드이다. 특별한 문제는 없어 보이지만, 이스택을 사용하는 프로그램을 오래 실행하다 보면 가비지 컬렉션 활동과 메모리 사용량의 증가로 성능이 저하할것 이며, 심할 경우 OutOfMemoeryError을 일으킬 것이다.
그 이유는 pop 메서드를 통해 특정 객체를 꺼내 사용하여 메모리를 해제하는것이 아니라 push 메서드 사용시 그 위치에 덮어 씌우는 방식으로 프로그래밍 되어 있기 때문이다.
가비지 컬렉터의 경우 객체 참조를 살려두면 그 객체가 참조하는 모든 객체를 회수해가지 않는다. 그렇기 때문에 배열에서 참조 해제 시켜주지 않는다면 메모리 누수가 일어날 것이다.
그렇다면 이런 코드의 참조 해제 방법은 무엇이 있냐면
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
위처럼 다 쓴 참조를 null 처리를 함으로 써, 메모리 누수가 없는 코드를 완성할 수 있다. 그거 의외의 이점으로, 만약 null 처리한 참조를 실수로 사용하려 하면 프로그램은 즉시 NullPointerException을 발생시켜 종료 시킬 것이다.
하지만 이는 코드를 필요 이상으로 더럽힐 것이다. 그렇기에 null 처리하는 일은 예외적인 경우여야 한다.
다 쓴 참조 해제중 가장 좋은 방법은 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다.
이외에도 캐시, 리스너, 콜백의 경우에도 메모리 누수를 발생시킬 것이다. 따라서 이런 메모리 사용시 꼭 사용 해제를 해주어야 한다.
메모리 누수는 수년간 시스템에 잠복하는 경우도 있다. 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원하여 발견되기도 한다. 그렇기에 이런 예방법을 익혀두는 것이 중요할 것이다.