-
객체 생성과 파괴 - 아이템 7. 다 쓴 객체 참조를 해제하라Study/Effective Java 2022. 12. 13. 09:44
다 쓴 객체 참조를 해제하라
메모리 누수 문제
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); } } }
Java는 가비지 컬렉터가 있지만 메모리 관리에 신경을 안 써도 되는 것은 아니다. 위 소스코드는 메모리 누수 문제가 존재한다.
스택이 커졌다가 줄어들었을 때 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않는다. 이 스택이 그 객체들의 다 쓴 참조를 여전히 가지고 있기 때문이다. elements 배열의 활성 영역 밖의 참조들을 가리킨다.
가비지 컬렉션 언어에서는 메모리 누수를 찾기 까다롭다. 객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐 아니라 그 객체를 참조하는 모든 객체를 회수해가지 못한다. 그래서 단 몇 개의 객체가 매우 많은 객체를 회수되지 못하게 할 수 있고 잠재적으로 성능에 악영향을 줄 수 있다.
public class Stack { ... public Object pop() { if (size == 0) { throw new EmptyStackException(); } Object result = elements[--size]; elements[size] = null; // 다 쓴 참조 해제 return result; } }
앞선 문제를 해결하기 위한 해법은 위와 같다. 바로, 해당 참조를 다 썼을 때 null 처리를 하면 된다. 다 쓴 참조를 null 처리하면 다른 이점도 따라온다. 만약 null 처리한 참조를 실수로 사용하려 하면 프로그램은 즉시 NullPointerException을 던지면 종료된다.
하지만, 객체 참조를 null 처리하는 일은 예외적인 경우여야 한다. 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다. 이 변수의 범위를 최소가 되게 정의했으면 자연스럽게 이루어진다.
메모리 직접 관리
Stack 클래스가 문제가 되었던 이유는 메모리를 직접 관리했기 때문이다. elements 배열로 저장소 풀을 만들어 원소들을 관리하였다. 배열의 활성 영역에 속한 원소들이 사용되고 비활성 영역은 쓰이지 않는다. 문제는 가비지 커렉터는 이 사실을 알 길이 없다는 데 있다. 가비지 컬렉터가 보기에는 비활성 영역에서 참조하는 객체도 똑같이 유효한 객체이다. 따라서, 프로그래머는 비활성 영역이 되는 순간 null 처리해서 해당 객체를 더는 쓰지 않을 것임을 가비지 컬렉터에 알려야 한다. 이렇게, 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항상 메모리 누수에 주의해야 한다.
캐시
캐시 역시 메모리 누수를 일으키는 주범이다. 객체 참조를 캐시에 넣고 나서, 이 사실을 까맣게 잊은 채 그 객체를 다 쓴 뒤로도 한참을 그냥 놔두는 일이 종종 발생한다.
키(key)를 참조하는 동안만 엔트리가 살아 있는 캐시를 필요로 하는 상황이라면 WeakHashMap을 사용해 캐시를 만드는 것이 좋다. 다 쓴 엔트리는 그 즉시 자동으로 제거될 것이다. 단, WeakHashMap은 이러한 상황에서만 유용하다는 사실을 기억하자.
최종 정리
메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다. 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다. 따라서, 이런 종류의 문제는 예방법을 익혀두는 것이 매우 중요하다.
출처
ㆍ 이펙티브 자바 Effective Java 3/E. 조슈아 블로크 저자(글) · 개앞맵시(이복연) 번역
728x90'Study > Effective Java' 카테고리의 다른 글
객체의 생성과 파괴 - 아이템 9. try-finally보다는 try-with-resource를 사용하라 (0) 2022.12.13 객체 생성과 파괴 - 아이템 8. finalizer와 cleaner 사용을 피하라 (0) 2022.12.13 객체 생성과 파괴 - 아이템 6. 불필요한 객체 생성을 피하라 (0) 2022.12.12 객체 생성과 파괴 - 아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) 2022.12.12 객체 생성과 파괴 - 아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) 2022.12.12