Item7
다 쓴 객체 참조를 해제하라
- C나 C++와는 달리, 자바는 가비지 컬렉터(GC)가 다 쓴 객체를 알아서 회수해 준다.
- 메모리 관리에 더 이상 신경 쓰지 않아도 된다고 생각할 수 있으나, 절대 사실이 아니다.
1 메모리 누수 예시
Stack.java
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 Stack(int size) {
this.elements = new Object[size];
}
public void push(final Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) {
return null;
}
return elements[--size];
}
public Object size() {
return this.elements.length;
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, (size * 2) + 1);
}
}
}
- 위 코드는 특별한 문제가 없어 보이고, 테스트를 수행해도 통과할 것이다.
- 하지만 메모리 누수라는 문제가 숨어있는데, 이 스택을 오래 실행하다 보면 점차 가비지 컬렉션 활동과 메모리 사용량이 늘어나 결국 성능이 저하될 것이다.
- 심한 경우 디스크 페이징이나 OutOfMemoryError를 일으켜 프로그램이 예기치 않게 종료되기도 한다.
1.1 원인
- 위 코드에서 스택이 커졌다가(push), 줄어들때(pop) 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않는다.
- 스택이 해당 객체들의 다 쓴 참조를 여전히 가지고 있기 때문이다
- 다 쓴 참조: 앞으로 다시 쓰지 않을 참조
- GC는 Unreachable한 객체를 찾을 수 있지만 Unused 한 객체를 찾을 수 없다
- 위에 다 쓴 참조가 Unused 한 객체이다
- Unused 한 객체는 프로그램의 논리에 따라 달라지므로 프로그래머는 비즈니스 코드에 주의해야 한다
1.2 해결
- 위 문제를 해결하려면 해당 참조를 다 썼을때 null 처리(참조 해제) 하면 된다.
// 기존 pop 메서드
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
// 수정된 pop 메서드
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
1.3 Java의 Stack 클래스
- 실제 Java의 Stack 클래스를 어떻게 구현되어 있을까?
Stack의 pop 메소드
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
Stack의 removeElementAt 메소드
elementData[elementCount] = null;다 쓴 참조를 해제하고 있는 것을 볼 수 있다
public synchronized void removeElementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
modCount++;
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}