-
[Effective Java] 아이템7: 다 쓴 객체 참조를 해제하라Language/Java 2022. 5. 21. 03:21
가비지 컬렉션 언어에서는 메모리 누수를 찾기가 아주 까다롭다.
가비지 컬렉터는 객체 참조 하나를 살려두면 그 객체 뿐만 아니라 그 객체가 참조하는 모든 객체(그리고 또 그 객체들이 참조하는 모든 객체...)를 회수해가지 못한다. 그래서 단 몇 개의 객체가 매우 많은 객체를 회수되지 못하게 할 수 있고, 잠재적으로 성능에 악영향을 줄 수 있다.스택을 구현하는 다음의 코드를 보자.
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); } }
이 코드에서 메모리 누수는 어디서 일어날까?
스택이 커졌다가 줄어들었을 때 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않아 메모리 누수가 발생한다.
이는 스택이 그 객체들의 다 쓴 참조를 여전히 가지고 있기 때문이다.해법은 해당 참조를 다 썻을 때 null 처리를 하여 참조를 해제하는 것이다.
예시에서는 pop 메서드에 원소를 꺼내고 더 이상 참조가 필요 없는 시점에 null 처리를 해주면 된다.public Object pop() { if(size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // 다 쓴 참조 해제 return result; }
이제 메모리 누수를 일으키는 주범들을 살펴보자.
✔️ 자기 메모리를 직접 관리하는 클래스
일반적으로 자기 메모리를 직접 관리하는 클래스라면 프로그래먼느 항시 메모리 누수에 주의해야 한다.
원소를 다 사용한 즉시 그 원소가 참조한 객체를 다 null 처리해줘야 한다.✔️ 캐시
객체 참조를 캐시에 넣고나서, 이 사실을 잊은 채 그 객체를 다 쓴 뒤로도 한참을 그냥 놔두는 일을 자주 접할 수 있다.
해법은 여러 가지다.캐시 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시가 필요한 상황이라면, WeakHashMap을 사용해 캐시를 만들자.
그러면 다 쓴 엔트리가 그 즉시 자동으로 제거될 것이다.시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 흔히 사용하는데, 이런 방식에서는 쓰지 않는 엔트리를 이따금 청소해줘야 한다.
백그라운드 스레드(Schedjuled ThreadPoolExecutor 같은)를 활용하거나 캐시에 새 엔트리를 추가할 때 부수 작업으로 수행하는 방법이 있다. LinkedHashMap은 removeEldestEntry 메서드를 써서 후자의 방식으로 처리한다.
더 복잡한 캐시를 만들고 싶다면 java.lang.ref 패키지를 직접 활용해야 할 것이다.✔️ 리스너 / 콜백
클라이언트가 콜백을 등록만 하고 명확하게 해지하지 않으면, 콜백이 계속 쌓여갈 것이다. 이럴 때 콜백을 약한 참조(weak reference)로 저장하면 가비지 컬렉터가 즉시 수거해간다. 예를 들어 WeakHashMap에 키로 저장하면 된다.
핵심 정리
메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다.
이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다.
그래서 이런 종류의 문제는 예방법을 익혀두는 것이 매우 중요하다.728x90'Language > Java' 카테고리의 다른 글
[Effective Java] 아이템9: try-finally 보다는 try-with-resources 를 사용하라 (0) 2022.05.30 [Effective Java] 아이템8: finalizer와 cleaner 사용을 피하라 (0) 2022.05.30 [Effective Java] 아이템6: 불필요한 객체 생성을 피하라 (0) 2022.05.21 [Effective Java] 아이템5: 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) 2022.05.21 [Effective Java] 아이템4: 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) 2022.05.14