아이템7. 다 쓴 객체 참조를 해제하라
2021-05-24 00:00:00 # Effective_Java
  • 다 쓴 객체를 알아서 회수해간다고 끝이 아니다.
  • Memory Leak (메모리 누수) Issue
  • 심할 때는 디스크 페이징이나 OutOfMemoryError를 일으킬 수 있다.

메모리 누수 예시 (원인1. 스택)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.Arrays;
import java.util.EmptyStackException;

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]; // 배열에서 객체가 반환되지는 않음 -> memory leak
}

private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1); // 스택이 커짐
}
}
  • 스택은 자기 메모리를 직접 관리한다.
    • 그래서 가비지 컬렉터가 스택 안에 있는 비활성화된 객체 영역을 알아차릴 수 없다.
  • 객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐 아니라 그 객체가 참조하는 모든 객체를 회수해가지 못한다.
  • 그래서 소수의 객체가 많은 객체에 영향을 주면서 성능에 악영향이 될 수 있다.

메모리 누수를 해결하려면…

  • 해당 참조를 다 썼을 때 null 처리 (참조 해제)
  • 그렇다고 모든 코드에 메모리 누수가 의심될 때마다 작성하면 코드가 더러워진다.
  • 객체 참조를 null 처리하는 일은 예외적인 경우여야 한다.
  • 다 쓴 객체 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 scope 밖으로 밀어내는 것이다.
  • 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항상 메모리 누수에 주의해야 한다.
  • 메모리 누수 관련 글

캐시 (원인2)

  • 객체 참조를 캐시에 넣고 나서, 이 사실을 까먹어서 방치될 수 있다.
  • 사용하지 않는 캐시 엔트리를 청소하는 방법을 사용한다.

리스너, 콜백 (원인3)

  • 클라이언트가 callback을 등록만 하고 명확히 해지하지 않는다면, 콜백은 계속 쌓인다.
  • 이런 상황에서 콜백을 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다. (WeakHashMap의 Key)

핵심 정리

  • 메모리 누수는 겉으로 잘 드러나지 않는다.
  • 발견하기도 정말 어려울 수 있기 때문에 예방법을 익히는 것이 매우 중요하다.