-
Item 7. 다 쓴 객체 참조를 해제하라백수의 개발/이펙티브 자바 2019. 7. 19. 14:45
C, C++처럼 메모리를 직접 관리해야 하는 언어와 달리 Java는 가비지 컬렉터(GC)를 갖추어 어느정도 알아서 메모리를 관리해준다.
그러나 메모리 관리에 아예 신경 쓰지 않아도 된다는 것은 아니다.
크게 메모리 누수에 문제를 발생시키는 것들이 3가지 있다. 어떤 상황에서 메모리 관리를 신경써줘야할지 알아보자.
자기 메모리를 직접 관리하는 클래스
아래 Stack을 구현한 코드에서 메모리 누수가 일어나는 위치가 어디인지 확인해보자.
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); } }
실제 스택이 pop을 할 때 GC가 꺼내진 객체들을 회수하지 않는다. 프로그램에서 그 객체들을 더 이상 사용하지 않더라도 말이다.
객체 참조 하나를 살려두면 GC는 그 객체뿐 아니라 그 객체가 참조하는 모든 객체를 회수하지 못한다.
GC가 메모리 회수를 할 수 있도록 하기 위해서는 참조 해제를 하면되는데, 이는 아래와 같이 null처리를 해주면 된다.
public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // 다 쓴 참조 해제 return result; }
Stack클래스 처럼 자기 메모리를 직접 관리하는 클래스인 경우 프로그래머는 항시 메모리 누수에 주의해야한다.
캐시
객체 참조를 캐시에 넣은 후, 이를 그냥 놔둔다면 메모리 회수가 되지 않는다.
이를 해결하기 위해서는 WeakHashMap을 사용한다면 외부에서 Key를 참조하는 동안만 엔트리가 살아 있어 Key참조가 해제되면 다 쓴 엔트리는 그 즉시 자동으로 제거된다.
리스너(Listener) 또는 콜백(Callback)
클라이언트가 콜백을 사용할 때 등록만 하고 명확히 해지하지 않는다면, 콜백은 계속 쌓이게 된다.
이를 해결해기 위해서는 약한 참조 즉, Weak Reference를 통해 저장하면 가비지 컬렉터가 알아서 수거해간다. 이것 또한 캐시와 같이 WeakHashMap을 활용하면 좋다.
마무리
메모리 누수는 겉으로 잘 드러나지 않는다. 그렇다보니 시스템에 수년간 잠복하는 경우도 있다.
이는 철저한 코드 리뷰나 힙 프로파일러와 같은 디버깅 도구를 동원해야 발견되기도 한다. 따라서 이러한 종류의 문제에 대해서 예방법을 익혀두는 것이 중요하다.
'백수의 개발 > 이펙티브 자바' 카테고리의 다른 글
Item 9. try-finally보다는 try-with-resource를 사용하라 (0) 2019.08.09 Item 8. finalizer와 cleaner 사용을 피하라 (0) 2019.08.07 Item 6. 불필요한 객체 생성을 피하라 (2) 2019.07.16 Item 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) 2019.07.15 Item 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) 2019.07.14