자바는 가비지 컬렉터가 있기 때문에 메모리 관리에 신경 쓰지 않아도 생각하기 쉽지만 그렇지 않다.
이 책에서는 자기 메모리를 직접 관리하는 클래스, 캐시, 리스터 또는 콜백의 경우에는 메모리 관리를 주의해야 한다고 설명한다.
메모리 누수의 주범인 위 세 가지 경우에서 메모리 누수가 발생하는 원인과 해결 방법에 대해 알아보자.
1. 자기 메모리를 직접 관리하는 클래스
다음 코드를 살펴보자. elements 배열로 저장소 풀을 만들어 원소들을 관리하기 때문에, 자기 메모리를 직접 관리하는 클래스임을 알 수 있다.
public class Stack {
...
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
}
위 코드는 스택의 크기가 줄어들더라도 가비지 컬렉터가 회수하지 않는다. 여전히 쓰지 않을 참조를 그대로 가지고 있기 때문이다.
객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐 아니라 그 객체가 참조하는 모든 객체(또 그 객체들이 참조하는 모든 객체)를 회수해가지 못한다. 즉, 몇 개의 객체가 매우 많은 객체를 회수하지 못할 수 있어 성능이 저하된다.
해결 방법 1. 해당 참조를 더 이상 사용하지 않은 경우 null 처리하여 가비지 컬렉터에게 명시적으로 알린다.
| 장점 | null 처리한 참조를 실수로 사용하려 하면 NullPointerException이 발생한다. |
| 주의할 점 | 필요없는 객체를 볼 때마다 null로 설정하는 것은 옳지 않다. (cuz 코드가 지저분해짐) 객체 참조를 null 처리하는 일은 예외적인 경우(ex. 자기 메모리를 직접 관리하는 클래스)여야 한다. |
해결 방법 2. 참조를 담은 변수의 범위를 최소화하여, 더 이상 필요하지 않은 시점에서 유효 범위를 벗어나게 한다.⭐
해당 방법으로 유효 범위를 벗어나면 자동으로 가비지 컬렉터에 의해 회수될 수 있도록 한다.
변수의 범위를 최소가 되게 정의했다면(아이템 57) 자연스럽게 이뤄진다.
위 코드를 올바르게 수정하면 다음과 같다.
public class Stack {
// ...
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
}
*디스크 페이징
가상 메모리 관리 기술 중 하나로, 프로세스가 필요한 메모리 공간을 물리적인 메모리보다 큰 가상의 메모리 공간으로 나누어 처리하는 것이다.
*OutOfMemoryError
자바 프로그램에서 메모리가 부족하여 객체를 생성할 수 없을 때 발생하는 예외이다.
*저장소 풀
컴퓨터에서 메모리나 디스크 공간 등의 자원을 미리 할당하여 필요한 시점에 빠르게 사용할 수 있도록 하는 기술이다.
2. 캐시
객체 참조를 캐시에 넣어놓고 캐시를 비우는 것은 잊기 쉽다.
해결 방법 1. WeakHashMap을 사용한다.
WeakHashMap은 캐시의 키에 대한 참조가 캐시 밖에서 필요 없어지면 해당 엔트리를 캐시에서 자동으로 비워준다.
캐시 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시가 필요한 경우에만 사용하는 것이 좋다.
Map<Key, Value> cache = new WeakHashMap<>();
public Value getValue(Key key) {
Value value = cache.get(key);
if (value == null) {
value = createValue(key); // 새로운 value 생성
cache.put(key, value);
}
return value;
}
그러나 보통 캐시를 만들 떄 캐시 엔트리의 유효 기간을 정확히 알기가 어렵다. 따라서 점차 엔트리의 가치를 떨어뜨리는 방식을 사용한다. 아래 방법 2, 방법 3을 살펴보자.
해결 방법 2. 백그라운드 스레드(ex. ScheduledThreadPoolExecutor)를 활용
private static final ScheduledExecutorService cacheCleaner = Executors.newScheduledThreadPool(1);
Map<Key, Value> cache = new HashMap<>();
public Value getValue(Key key) {
Value value = cache.get(key);
if (value == null) {
value = createValue(key); // 새로운 value 생성
cache.put(key, value);
scheduleEviction(key); // 가치를 떨어뜨리는 작업 예약
}
return value;
}
private void scheduleEviction(Key key) {
// 캐시 엔트리의 가치를 떨어뜨리는 작업을 예약
cacheCleaner.schedule(() -> {
cache.remove(key); // 엔트리 제거
}, EXPIRATION_TIME, TimeUnit.MILLISECONDS);
}
해결 방법 3. 캐시에 새 엔트리를 추가할 때 부수 작업으로 수행(ex. LinkedHashMap)
더 복잡한 캐시를 만들고 싶다면 java.lang.ref 패키지 활용한다.
Map<Key, Value> cache = new LinkedHashMap<Key, Value>(CACHE_CAPACITY, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<Key, Value> eldest) {
// 캐시 엔트리의 가치를 떨어뜨리는 작업 수행 후 결과 반환
return size() > CACHE_CAPACITY;
}
};
public Value getValue(Key key) {
Value value = cache.get(key);
if (value == null) {
value = createValue(key); // 새로운 value 생성
cache.put(key, value);
}
return value;
}
*엔트리(Entry)
Map에서 키-값(key-value) 쌍으로 구성된 하나의 요소를 의미한다.
*weak reference
자바에서 제공하는 참조 유형 중 하나로, 가비지 컬렉터의 수거 대상이 될 수 있는 객체를 참조한다. 참조되는 객체가 null이 되거나, 더 이상 참조되지 않을 때, 가비지 컬렉터는 해당 객체를 수거하고 메모리를 반환한다. java.lang.ref.WeakReference 클래스를 통해 사용할 수 있으며, 수거될 때 콜백 메소드를 작성할 수 있다.
자바 참조 유형이 총 4개가 있는데, 유형에 따라 GC 실행 대상 여부가 다르므로 궁금하다면 아래 링크를 참고하자.
(Java) 참조 유형 (Strong Reference/ Soft Reference/ Weak Reference/ Phantom References)
Java Strong Reference/ Soft Reference/ Weak Reference/ Phantom References Java의 참조 유형에는 크게 4가지가 있습니다. 참조 유형에 따라 GC 실행 대상여부, 시점이 달라집니다. 1. Strong References (강한 참조) 2. Soft Re
lion-king.tistory.com
3. 리스너 or 콜백
클라이언트가 콜백을 등록만 하고 해지를 하지 않는다면 콜백이 쌓이게 된다.
해결 방법 1. 콜백을 약한 참조(WeakHashMap)로 저장하여 가비지 컬렉터가 즉시 수거하도록 한다.
*callback
함수나 메소드를 다른 코드에서 호출 가능하게 만드는 기법으로, 비동기적 프로그래밍이나 이벤트 기반 프로그래밍에서 유용하게 사용된다.
*listener
이벤트를 처리하는 객체로, 특정 이벤트가 발생했을 때 실행될 메소드를 정의하여 이벤트 처리를 수행한다.
'Language > Java' 카테고리의 다른 글
| [effective java] 아이템 9. try-finally보다는 try-with-resources를 사용하라. (0) | 2023.03.08 |
|---|---|
| [effective java] 아이템 8. finalizer와 cleaner 사용을 피하라. (0) | 2023.03.08 |
| [effective java] 아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라. (0) | 2023.03.02 |
| [effective java] 아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라. (0) | 2023.03.02 |
| [effective java] 아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라. (0) | 2023.03.02 |