메모리 누수 발생
객체 참조가 존재한다면 GC(가비지 컬렉터)가 객체 회수하지 못함.
예시)
public class Stack {
private Object[] elements;
private int size = 0;
...
//메모리 누수 발생
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elemets[--size];
}
}
위 상황에서 element의 원소가 계속 객체를 참조하고 있기 때문에 GC가 회수하지못함
-> 메모리 누수 발생
메모리 누수가 발생한다면 메모리 사용량 증가 -> 성능 저하 유발
심한경우 디스크 페이징, OutOfMemoryError이 발생할 수 있음
참조 해제 (null 처리)로 해결
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
// 참조 해제
elements[size] = null;
return result;
}
위 Stack에서 메모리 누수가 발생한 이유는 자기 메모리를 직접 관리하기 때문임
프로그래머가 직접 null 처리를 통해 가비지 컬렉터에게 알려주어야 함
객체 참조를 Null로 처리하는 일은 예외적인 경우여야 함.
참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내라
- 변수의 범위를 최소가 되게 정의해야함 (자동으로 변수가 할당해제가 될 수 있도록)
캐시의 메모리 누수
객체 참조를 캐시에 넣고 해당 객체를 다 쓰고도 놔두면 GC가 객체를 회수할 수 없게 됨 (참조하고 있기 때문에)
해결책
1. WeakHashMap 사용해 자동 할당 해제 (weak reference가 사용됨)
weak reference는 해당 객체 참조가 없다면 자동으로 GC가 회수함
2. ScheduledThreadPoolExecutor 등으로 백그라운드 스레드 활용하여 엔트리를 청소한다
3. LinkedHashMap의 removeEldestEntry를 overriding 하기
실제 구현부 코드를 살펴봅시다.
...
// linked hashmap 구현부 일부
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
...
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
위의 removeEldestEntry를 map의 size와 연관해서 오버라이딩한다면 일정 크기가 넘어갈 때 자동으로 오래된 객체를 할당 해제할 수 있습니다.
예시)
public class MyLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 5;
public MyLinkedHashMap(
int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
}
리스너, 콜백의 메모리 누수
클라이언트가 콜백을 등록만 하고 명확한 해지를 하지않으면 콜백은 계속 쌓여갈 수 있다.
weak reference로 콜백을 저장하면 GC가 즉시 수거해가도록 할 수 있다.