예시로 Java로 구현한 Stack의 코드를 살펴보자.
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(){
ensured = 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);
}
size
를index
로 사용하고 있다. 쌓여 있던stack
에pop
을 계속하여도stack
이 차지하는 메모리는 줄어들지 않는다.
public Object pop(){
if (size ==0)
throw new EmptyStackException();
Object value = this.elements[--size];
this.elements[size] == null;
return value;
}
pop
을 통해 스택을 꺼낸 뒤, 해당 자리를null
로 설정해GC
가 발생할 때, 메모리가 정리되도록 코드를 변경할 수 있다.
주의점
매번, 변수를 사용하고 null
값으로 변경하는 번거로운 과정은 않다도 된다. 예를 들어서 지역 변수로 선언된 변수들은 해당 스코프를 벗어나면 의미없는 래퍼런스 변수가 되어 GC
에 의해서 정리되기 때문이다.
메모리를 직접 관리할 때
캐시 역시 메모리 누수를 일으키는 주범이다. 캐시 외부에서 키를 참조하는 동안만 엔트리가 살아 있는 캐시가 필요한 상황이라면 WeakHashMap
을 사용하자. 다 쓴 엔트리는 자동으로 GC
에 발생시 제거될 것이다.
마지막으로 흔하게 메모리 누수가 발생할 수 있는 지점으로 리스너와 콜백이 있다. 콜백을 Weak reference
에 저장하자.
Strong reference
로 객체를 생성했어도 Weak reference
로 감싼다면, GC
발생시 메모리가 수거될 수 있다.
Object key1 = new Object();
Object value1 = new Object();
/** **/
Map<Object, Object> cache1 = new HashMap<>();
cache1.put(key1, value1);
/** Weak reference로 감싸기**/
Map<Object, Object> cache2 = new WeakHashMap<>();
cache2.put(key1, value1);
첫번째 cache1은 더이상 key1 객체를 참조하지 않아도
GC
의 대상이 될 수 없다. 하지만, cache2는 앞서 말했듯이Weak reference
로 감쌌기 때문에,GC
의 대상이 될 수 있다.