자바는 C나 C++과 다르게 쓰레기 수집가가 메모리를 관리해준다.
쓸모없어진 주소값을 자동으로 폐기해줘서 수동으로 관리해주는 C,C++ 보다 편하다고 할 수 있다.
하지만 자바에서도 간간히 주소값을 관리해주는 경우가 있다.
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을 함과 동시에 데이터를 초기화 시키지 않는다.
그림으로 한번 봐보자.
그림판 작업이므로 엉성하지만..
스택에 숫자 1,2,3,4,5가 저장되어 있다고 해보자
5 값을 사용할 일이 있어서, pop 메소드를 통해 꺼냈다.
스택의 size는 4가 되었다. 하지만 5값은 그대로 남아서, 메모리를 잡아먹고 있는 것이다.
이렇게 의도되지 않는 메모리는 특별한 증상을 보이지 않아서 찾기 힘든 메모리 누수이다.
처음 작성할 때 주의깊게 작성하는 수밖에 없다.
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object results = elements[--size];
elements[size] = null;
return results;
}
여기서는 이렇게 해주는 것이 좋다.
pop으로 스택의 값을 직접 돌려주기 전에 값을 저장해 둔 뒤 스택 공간을 null로 초기화 시킨 뒤
저장시킨 값을 돌려주는 형태이다.
또 다른 메모리 누수가 잘 일어나는 곳은 캐시(cache)이다. 객체 참조를 캐시 안에 넣어놓고
잊어버리는 일이 잦기 때문이다.
이러한 경우 WeakHashMap을 통해서 관리가 가능하다.
또 다른 경우로는 리스너 등의 역호출자(callback)이다.
역호출자 등록을 사용하는 API 등이 호출을 끝낸 뒤 명시적으로 제거하지 않을 경우에
메모리는 점유된 상태로 남게 되고, 이는 메모리 누수로 이어진다.
역호출자에 대한 가장 좋은 예방방법 역시 weak reference이다.
위의 weakHashMap에서 사용하는 그것이다.
WeakHashMap에 대해서 구글링 해보다가 좋은 문서가 있어서 영어 공부겸 번역해봤다.
개념에 자세하지 않는 분들은 읽어보면 도움이 될 것 같다.
1. 개요
이 포스트에서 우리는 java.util.package의 WeakHashMap를 살펴볼 것이다,
데이터 구조의 이해를 위해, 우리는 간단한 캐쉬를 구현해 볼 것이다. 그러나 이것은 맵의 동작을 이해하기 위함임을 명심하라.
이 방법으로 캐쉬를 구현하는 것은 좋지 않은 아이디어이다. (almost always a bad idea)
간단하게, WeakHashMap은 맵 인터페이스의 구현을 기반으로 한 해쉬 테이블이다.
다만 키 값이 약한 참조(WeakReference)를 가진다는 점에서 차이가 있다.
WeakHashMap에 넣은 값은 키 값이 더이상 사용되지 않을 때 제거된다. 이 말인 즉, 그 키 값을 가리키는 참조값이 사라졌을 때를 이야기 한다.
쓰레기 수집자 프로세스(이하 GC)가 키 값을 폐기할 때, 그 값은 효율적으로 맵에서 제거된다. 그래서 이 클래스는 다른 맵 구현과 다소 다르게 동작한다.
2. Strong, Soft, and Weak References
WeakHashMap이 어떻게 동작하는지 이해하기 위해 우리는 WeakHashMap 구현의 기본이 되는
약한 참조 클래스(WeakReference Class)를 살펴보아야 한다.
자바에는 세가지 참조 타입이 있다. 다음 섹션에서 상세히 설명하겠다.
2.1 Strong References
강한 참조(Strong reference)는 우리가 프로그래밍을 하면서 가장 흔하게 볼 수 있는 참조 타입이다.
변수 prime은 값 1을 가진 Integer에 강한 참조를 가진다. 이러한 강한 참조를 가진 오브젝트라면, GC는 수집하지 않는다.
2.2 Soft References
간단하게 설명하자면 어떤 자료형에 부드러운 참조(SoftReference)를 가진 객체는
JVM이 메모리가 필요해지기 전까지는 GC가 수집하지 않는다.
우리가 자바에서 부드러운 참조형을 어떻게 만들 수 있는지 다음 코드를 통해 살펴보자
prime 변수는 그 값에 강한 참조를 가진다.
다음으로 우리는 prime을 약한 참조로 감쌌다(wrapping). 강한 차모형을 null 값으로 만든 뒤, prime 객체는 GC의 작업대상이 된다.
하지만 JVM이 메모리가 필요할 때까지 수집되지 않는다.
2.3 Weak References
약한 참조(Weak references)에 의해 참조를 가진 객체는 그 즉시 GC의 대상이 된다.
GC는 메모리가 필요하게 될 때까지 기다리지도 않는다.
한번 WeakReference 코드를 만들어 보자
우리가 preime 참조를 null로 만들었을 때, 강한참조가 사라짐으로서 prime 객체는 다음 GC의 순환(cycle) 과정에서 바로 수집된다.
이 WeakReference 타입의 참조들이 WeakHashMap의 키로 사용되는 것이다.
3. 효율적인 캐쉬 메모리 관리를 위한 WeakHashMap
이미지를 값으로, 키는 이미지 이름으로 가지는 캐쉬를 빌드한다고 생각해보자. 우리는 적절한 맵 구현을 사용하여 문제를 해결할 수 있다.
이미지 값이 많은 메모리를 차지하기 때문에 단순한 HashMap을 사용하는 것은 좋은 방법이 아니다.
심지어, 그 값이 더이상 그 어플리케이션에서 사용되지 않을 때에도 그것들은 절대 GC의 수집대상이 되지 않을 것이다.
우리는 사용하지 않는 객체는 깔끔하게 지워주는 맵 구현을 원할 것이다.
이미지의 키 값이 더이상 어플리케이션에서 사용되지 않을 때 그 값은 메모리에서 지워주는 그런 이상적인 Map을 원할 것이다.
다행히 WeakHashMap이 정확히 그 특성을 가진다.
다음 코드로 WeakHashMap이 어떻게 동작하는지 살펴보자
우리는 큰 이미지를 저장하는 WeakHashMap 인스턴스를 만들었다. 이미지 값은 값으로, 이미지의 이름은 키값으로 가진다.
이미지의 이름은 맵에서 WeakReference 타입으로 저장된다.
다음으로 우리는 이미지 이름 값을 null 값으로 바꿔준다. 그러면 더 이상 이미지를 가리키는 참조값은 없어 질 것이다.
WeakHashMap의 기본 행위자(default behavior)는 더 이상 참조값을 가지지 않는 그 값을 제거해달라 GC에게 요청할 것이다.
그래서 그 값은 다음 GC 프로세스 순환에서 메모리에서부터 제거된다.
우리는 System.gc()를 호출해서 강제적으로 GC 프로세스를 발동(trigger)시키고 있다.
GC 순환(circle) 이후 우리의 WeakHashMap읜 텅 비게 될 것이다.
imageNameFirest의 참조가 null이 되었을 때 imageNameSecond의 참조는 변하지 않음을 명심해라. GC가 발동된 후
Map은 오직 imageNameSecond 값만을 가지게 된다.
4. 결론
이 포스트에서 우리는 java.util.WeakHashMap이 어떻게 작동하는지 이해하기 위해 참조의 타입들을 살펴보았다.
우리는 우리가 예상한 것 처럼 작동하는지 확인하기 위해 WeakHashMap의 행위를 유발(leverages)하는 간단한 캐쉬도 만들고 테스트도 해보았다.
모든 예제의 구현과 코드는 Maven project인 GitHub project에서 찾을 수 있다. 간단하게 실행해보길 바란다.
원문: http://www.baeldung.com/java-weakhashmap
GitHub project: https://github.com/eugenp/tutorials/tree/master/core-java/src/test/java/com/baeldung/weakhashmap
'프로그래밍 > Java' 카테고리의 다른 글
이펙티브 자바 규칙 7 - 종료자 사용을 피하라 (0) | 2018.02.02 |
---|---|
이펙티브 자바 규칙 5 - 불필요한 객체 만들지 않기 (0) | 2018.01.28 |
이펙티브 자바 규칙 4 - 객체 생성은 private 생성자로 막자 (0) | 2018.01.27 |