C나 C++ 처럼 손수 메모리를 관리 해야 하는 언어를 쓰다 가비지 컬렉터가 있는 언어를 사용하게 되면 프로그래밍이 아주 쉬워진다.
하지만 필자 생각엔 어떤 언어든 메모리 관리가 제일 중요하다고 생각한다.
자바나 C#에서 가장 큰 고민은 얼마나 효율적으로 코드를 구현할 것인가 인 것 같다.
이 이야기는 재사용해야 하는 객체는 재사용하고 폐기하여 반환해야 하는 객체는 반환해야 한다는 것이다.
초급 개발자와 중급, 고급 개발자에 차이점도 이 부분에서 나타나는 것 같다.
아래의 간단한 스택(stack) 구현 사례를 보자.
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 29 30 | // 메모리 누수(memory leak)가 어디서 생기는지 보이는가? 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); } } } | cs |
코드 자체로는 아무 문제가 없다.
하지만 이 코드는 메모리 누수(memory leak) 문제가 있다.
가비지컬렉터의 경우 해당 객체에 참조되는 다른 객체들도 메모리 수집에 제외된다.
다음은 수정된 코드이다.
1 2 3 4 5 6 7 8 9 10 | public Object pop() { if(size == 0) { throw new EmptyStackException(); } Object result = elements[--size]; elements[size] = null; //참조 제거 return result; } | cs |
이 같은 코드를 구현하다보면 모든 객체에 null 처리하는 경우가 생길 수도 있는데, 꼭 그럴 필요는 없다.
참조 객체와 다르게 일반 객체는 생명주기를 생각하여 작성하면 된다.
캐시(cache)도 메모리 누수가 흔히 발생하는 장소이다.
해결책은 다음과 같다.
1. WeakHashMap을 가지고 캐시를 구현(이 경우 외부에서 참조가 완료되면 자동으로 키-값이 삭제 외부 참조의 따라 수명이 결정되는 상황에만 적용)
2. 일반적인 경우 후면 스레드(background thread) 등을 사용하여 작업(LinkedHashMap 클래스를 사용하면 접근법을 구현하기 좋다고 한다 removeEldestEntry 함수가 제공되기 때문 좀 더 복잡한 경우 java.lang.ref를 직저접 사용해야 할 수도 있다.)
메모리 누수가 흔히 발견되는 또 한 곳은 리스터(listener) 등의 역호출자(callback)이다.
API를 사용하는 클라이언트가 역호출자를 명시적으로 제거하지 않을 경우, 적절한 조치를 취하기 전까지 메모리는 점유된 상태로 남아있게 된다.
이 경우 역호출자에 대한 약한 참조(weak reference) 나 이보다 조금 강한 참조인 WeakReference를 사용하는 것이 좋다.
'Java' 카테고리의 다른 글
Effective Java #08 equals를 재정의할 때는 일반 규약을 따르라 (0) | 2016.04.17 |
---|---|
Effective Java #07 종료자 사용을 피하라 (0) | 2016.04.13 |
Effective Java #05 불필요한 객체는 만들지 말라 (0) | 2016.04.13 |
Effective Java #04 객체 생성을 막을 때는 private 생성자를 사용하라 (0) | 2016.04.13 |
Effective Java #02 private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하라. (0) | 2016.04.13 |