c, c++처럼 메모리를 직접 관리해야 하는 언어를 쓰다가 자바처럼 가비지 컬렉터를 갖춘 언어로 넘어오면 평안한다
다 쓴 객체를 알아서 회수해가기 때문이다
그러나 메모리 관리에 더 이상 신경 쓰지 않아도 된다고 오해하면 안 된다
7-1 메모리 누수가 일어나는 위치는 어디인가?
// 코드 7-1 메모리 누수가 일어나는 위치는 어디인가? (36쪽)
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = 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);
}
}
- 이 스택을 사용하는 프로그램을 오래 실행하면 가비지 컬렉션 활동과 메모리 사용량이 늘어나 성능 저하가 될 것이다
- 심할 경우에는 디스크 페이징이나 OutOfMemoryError를 일으켜 프로그램이 예기치 않게 종료된다
- 가비지 컬렉션 언어에서는 메모리 누수를 찾기가 아주 까다롭다
- 객체 참조 하나를 살려두면 가비지 컬렉터는 참조하는 모든 객체를 회수해 가지 못한다
- 해당 참조를 다 썼을 때 null 처리하면 된다 스택 클래스에서 각 원소의 참조가 더 이상 필요 없을 때 스택에서 꺼내진다
7-2 제대로 구현한 pop 메서드
// 코드 7-2 제대로 구현한 pop 메서드 (37쪽)
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
- 객체 참조를 null 처리하는 일은 예외적인 경우여야 한다
- 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다
1. 그렇다면 null 처리는 언제 해야 할까? stack 클래스는 왜 메모리 누수에 취약한 걸까?
- 스택이 자기 메모리를 직접 관리하기 때문이다
- 스택은 elements 배열로 저장소 풀을 만들어 원소들을 관리한다
- 배열의 활성 영역에 속한 원소들이 사용되고 비활성 영역은 쓰이지 않는다
- 가비지 컬렉터는 이사실을 알지 못하기 때문에 null 처리해서 해당 객체를 더는 쓰지 않는 것을 알려야 한다
- 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 한다
2. 캐시 역시 메모리 누수를 일으키는 주범이다
- 보통 캐시 엔트리의 유효 기간을 정확히 정의하기 어렵기 때문에 시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 흔히 사용한다
- 이런 방식은 쓰지 않는 엔트리를 청소해줘야 한다 (ScheduledThreadPoolExecutor)
- 백그라운드 스레드를 활용하거나 캐시에 새 엔트리를 추가할 때 부수 작업으로 수행하는 방법이다
- LinkedHashMap은 removeEldestEntry 메서드를 써서 후자의 방식으로 처리한다
3. 메모리 누수의 세 번째 주범은 바로 리스너 혹은 콜백이다
- 클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면, 뭔가 조치해주지 않는 한 콜백은 쌓여간다
- 이럴 때 콜백을 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다 (WeakHashMap) 키로 저장
메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다. 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다. 그래서 이런 종류의 문제는 예방법을 익혀두는 것이 매우 중요하다.
'Books > Effective-Java 3판' 카테고리의 다른 글
9.try-finally보다는 try-with-resources를 사용하라 (0) | 2021.09.27 |
---|---|
8.finalizer와 cleaner 사용을 피하라 (0) | 2021.09.25 |
6.불필요한 객체 생성을 피하라 (0) | 2021.09.23 |
5.자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2021.09.18 |
4.인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2021.09.16 |