핵심 정리
- 메모리 누수는 겉으로 잘 드러나지 않아 코드 리뷰나 힙 프로파일러와 같은 디버깅 도구를 사용하여 파악하자.
- 메모리 누수와 관련된 문제에 대한 예방법을 익혀두자.
- 자바 GC가 모든 것을 해결해 주지 않는다. 참조를 해제하는 습관을 가지자.
사용이 끝난 객체 참조는 명시적으로 제거하자
경우 1. 스택과 같이 내부적으로 메모리를 관리하는 자료 구조
public class Stack {
private Object[] elements;
private int size = 0;
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);
}
}
- 자바는 GC을 지원하여 메모리를 직접 해제할 필요는 없지만 객체를 수거하지 못하도록 방해하는 구조가 있다.
- 바로 `Stack`과 같이 자기 메모리를 직접 관리하는 구조일 경우 해당한다.
- 위 코드에서 스택에서 꺼내진 객체들은 참조가 남아있어 GC가 회수하지 않는 문제점을 가지고 있다.
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
- 불필요해진 참조를 null로 설정하여, GC가 해당 객체를 수거할 수 있도록 하자.
- 하지만 null을 처리하기 위해 필요 이상으로 코드가 지저분해 질 수 있다.
- 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이 가장 권장하는 방법이다.
경우 2. 캐시
Map<Key, Value> cache = new WeakHashMap<>();
- 캐시 역시 메모리 누수를 일으키는 주범이다.
- 객체 참조를 캐시에 넣어 두고, 필요가 없어질 경우에 한참 놔두는 경우가 허다하다.
- 그렇기에 위 코드와 같이 `new WeakHashMap<>()`을 사용하여 GC가 참조를 제거할 수 있도록 하자.
- 엔트리를 다 쓸 경우, 자동으로 제거되지만 캐시 엔트리 유효 기간을 정확히 정의하긴 어렵다.
- 시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 사용하자 - ex) Scheduled ThreadPoolExecutor
경우 3. 리스너(Listener) 혹은 콜백(Callback)
- 클라이언트가 콜백을 등록하고 명확히 해지하는 경우는 많다.
- 콜백을 약한 참조로 저장하면 GC가 수고할 수 있다.
- 예로 WeakHashMap에 키를 저장하면 해결할 수 있다.
'Java > Effective Java' 카테고리의 다른 글
[이펙티브 자바] Item 9. try-finally보다 try-with-resource를 사용하라 (0) | 2025.03.24 |
---|---|
[이펙티브 자바] Item 8. finalizer와 cleaner 사용을 피하라 (0) | 2025.03.24 |
[이펙티브 자바] Item 6. 불필요한 객체 생성을 피하라 (0) | 2025.03.24 |
[이펙티브 자바] Item 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2025.03.24 |
[이펙티브 자바] Item 4. private 생성자를 사용하여 인스턴스화를 막아라 (0) | 2025.03.23 |