메모리 누출로 인해 가비지 컬렉션 작업이 증가하면서 메모리 할당과 회수가 빈번하게 생겨서 성능 저하가 발생할 수 있다. 최악의 경우 디스크 상의 paging이나 OOME까지 발생할 수도 있다.
메모리 누출이 생기는 쓸모 없는 참조가 발생하는 경우인데, 참조값이 null이 아닌 값을 갖고 있지만, 다시는 사용되지 않을 참조를 의미한다. 이 경우 참조값이 null이 아니기 때문에 가비지컬렉션 대상에서 제외되어 회수되지 않는다.
책에서는 stack을 통해 예를 들고 있다.
import java.util.EmptyStackException;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 4;
public Stack() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
// 1. 데이터를 입력하고,
public void push(Object o) {
System.out.println("PUSH : " + o);
elements[size++] = o;
}
// 2. 데이터를 갱신한다.
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
System.out.println("current stack size : " + --size);
return elements[size];
}
public void printAll() {
for (Object o : elements) {
System.out.print(o + " ");
}
System.out.println();
}
public static void main(String[] args) {
Stack s = new Stack();
s.push(1);
s.push(2);
s.printAll();
s.pop();
s.printAll();
s.push(3);
s.printAll();
}
}
PUSH : 1
PUSH : 2
1 2 null null
current stack size : 1
1 2 null null
PUSH : 3
1 3 null null
따라서 아래와 같이 바꿔줄 수 있다.
~
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null; // null로 변경해주었다.
System.out.println("current stack size : " + size);
return result;
}
PUSH : 1
PUSH : 2
1 2 null null
current stack size : 1
1 null null null
PUSH : 3
1 3 null null
비추
cache
객체 참조를 캐시에 저장하게 되면 캐시에 저장했다는 사실을 잊는 경우가 생긴다.
1. 캐시 외부에 캐시의 키에 대한 참조가 있을 동안만 캐시에 저장된 항목이 유효한 캐시를 구현하는 것으로 충분하면 weakHashMap을 캐시로 사용한다. 그러면 키로 저장된 캐시가 더이상 참조되지 않을 때 해당 항목이 자동으로 삭제 될 것이다. 단 생명주기가 외부 참조에 의해 결정되도록 할 때만 저 해시맵이 유용하다.
메모리 누출의 경우 명백하게 드러나지 않는 경우가 많기 때문에, 코드를 철저하게 검사하거나 heap profiler와 같은 디버깅 도구의 도움으로 원인을 분석하게 되는 경우가 있는데, 이러한 일이 생기지 않도록 미연에 방지하는 것이 매우 중요하다.