다 쓴 객체의 참조를 해제하라
자바는 가비지 컬렉터가 있지만 메모리 관리에 신경을 안써도 되는 것은 아니다.
- 어떤 객체에 대한 래퍼런스가 남아있다면 해당 객체는 가비지 컬렉션의 대상이 된다
- 자기 메모리를 직접 관리하는 클래스라면 메모리 누수에 주의해야 한다.
- 스택, 캐시, 리스너 또는 콜백
- 참조 객체를 null 처리하는 일은 예외적인 경우이며 가장 좋은 방법은 유효 범위 밖으로 밀어내는 것이다.
위의 메모리 누수에 주의해야 하는 요소들의 공통점은 어디선가 리스트, 컬렉션, 맵, 셋 등을 이용해서 데이터를 담고 있다는 공통점이 존재하며 해결하는 방식이 3가지가 존재한다.
null로 처리하기
// 메모리 누수가 일어나는 위치는 어디인가? 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; } // 올바르지 않은 pop 메소드 // 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 메서드 public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // 다 쓴 참조 해제 return result; } public static void main(String[] args) { Stack stack = new Stack(); for (String arg : args) stack.push(arg); while (true) System.err.println(stack.pop()); } }
- 스택은 메모리 누수에 취약하다. 그 이유는 스텍이 자기 메모리를 직접 관리하기 때문이다.
- 스텍은 객체 자체가 아닌 객체 참조를 담는 배열로 저장소 풀을 만들게 된다.
- 배열의 활성 영역에 속한 원소들이 사용되고 비활성 영역은 쓰이지 않는다.
- 하지만 가비지 컬렉터는 이 사실을 알 방법이 없다. 즉 프로그래머 만이 알고 있다.
- 따라서 프로그래머가 비활성 영역이 되는 순간에 null처리를 하여 해당 객체를 더 쓰지 않을 것을 알려줘야 한다.
- 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의하도록 하자
캐시 역시 메모리 누수를 일으키는 주범이다.
- 객체 참조를 캐시에 넣고 객체를 다 쓴 뒤에도 한참 놔두는 일이 자주 생긴다.
Object key = new Object(); Object value = new Object(); Map<Object, List> cache = new HashMap<>(); cache.put(key, value);
- key가 사라지면 이 캐싱 자체는 무의미해진다.
Object key = new Object(); Object value = new Object(); Map<Object, List> cache = new WeakHashMap<>(); cache.put(key, value);
키를 Weak라는 래퍼런스로 감싸서 들어가게 된다. 하드 래퍼런스(new로 생성한 일반적인 객체생성)가 없어지면 자동으로 정리된다. 캐시 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시가 필요한 상황이라면 WeakHashMap을 사용해 캐시를 만들도록 하자. 다 쓴 앤트리는 자동으로 제거된다.
- 캐시를 만들 때 보통 캐시 앤트리의 유효 기간을 정확히 정의하는 것은 어렵기 때문에 시간이 지날 수록 앤트리의 가치를 떨어뜨리는 방법을 쓰기도 한다.
콜백
- 메모리 누수의 또 다른 원인은 리스너 혹은 콜백이다. 콜백 등록만 하고 해지하지 않으면 콜백은 쌓여만 간다.
Weak Reference
- 가비지 컬렉터 대상이 되려면 그 객체를 가리키는 래퍼런스가 전부 없어져야 한다.
WeakReference weakWidgte = new WeakReference(widget);
widget이 없어지면 WeakReference가 차지하는 메모리 자체가 없어질 수 있다.
모든 경우에 그러는건 아니다 반드시 다쓴 객체의 참조를 해제해야 한다는건 아니다 변수를 만들고