728x90
반응형
아이템 7 다 쓴 객체 참조를 해제하라
C,C++: 메모리 직접 관리 / Java: GC가 더 이상 사용 X 객체 회수
-> 메모리 관리에 신경 쓰지 않아도 된다고 오해
메모리 누수 예시: 스택 구현
public class Stack {
private Ojbect[] 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];
}
/**
* 원소를 위한 공간을 적어도 1개 이상 확보
* 배열 크기를 늘려야 할 때마다 2배씩 늘림
*/
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
- 겉보기에 문제 X But, 메모리 누수 발생할 수 O
- 스택 커졌다 줄어들면, 스택에서 꺼내진 객체도 `elements` 배열에 여전히 참조가 남아 GC가 회수 X
- => 오랫동안 실행하면 GC 활동 ↑ + 메모리 사용량 ↑ -> 성능 저하
- 심하면 디스크 페이징 or `OutOfMemoryError` 발생 가능
다 쓴 참조(Obsolete Reference)
- 앞으로 다시 사용되지 X 참조
- `elements` 배열의 활성 영역(인덱스 < size) 밖의 객체 참조
- GC) 이런 참조 남아 있으면 객체 + 그 객체가 참조하는 모든 객체 회수 X
- 객체 + 객체가 참조하는 모든 객체(또 그 객체가 참조하는 모든 객체...)
-> 단 몇 개의 객체가 매우 많은 객체 회수 못하게 할 수 O
- 객체 + 객체가 참조하는 모든 객체(또 그 객체가 참조하는 모든 객체...)
개선된 pop 메서드
해당 참조 다 썼을 때 `null` 처리(참조 해제)
각 원소의 참조가 더 이상 필요 X 시점: 스택에서 꺼내질 때
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
- 참조를 `null`로 해제하면 잘못 사용 -> `NullPointerException` 발생해 버그 조기에 발견 O
- But, 객체 참조 무분별하게 `null` 초기화 좋지 X
- 가장 좋은 방법: 참조 변수를 최소한의 유효 범위(scope)로 정의하는 것
`Stack`이 메모리 누수에 취약한 이유
- 자기 메모리를 직접 관리
- (객체 자체 X 객체 참조를 담는) `elements` 배열로 저장소 풀을 만들어 객체 관리
- GC) 배열의 어느 부분이 활성 영역인지 알 수 X -> 비활성 영역의 참조도 유효하다고 판단
=> 비활성 영역으로 전환되는 순간 해당 참조를 `null`로 처리해서 해당 객체를 더 사용 X GC에게 알려야 함
다른 메모리 누수 예시와 해결 방법
1. 캐시(Cache)
- 객체 참조를 캐시 넣은 뒤 잊어버림 => 메모리 누수 발생
- (해결)
- 캐시 외부에서 키를 참조하는 동안만 엔트리가 살아 있어야 한다면
-> `WeakHashMap` 사용 (다 쓴 엔트리 자동 제거) - 유효 기간 명확 X 캐시
- 백그라운드의 스레드(ex. `Scheduled ThreadPoolExecutor`)
- `LinkedHashMap`은 `remove ElestEntry` 메서드로 관리
(캐시에 새 엔트리를 추가할 때 부수 작업으로 수행하는 방법) - 더 복잡한 캐시 만들고 싶으면 `java.lang.ref` 패키지 직접 활용
- 캐시 외부에서 키를 참조하는 동안만 엔트리가 살아 있어야 한다면
2. 리스너(listener) or 콜백(callback)
- 클라이언트) 콜백 등록, 명확히 해지 X -> 콜백 계속 쌓임 => 메모리 누수 발생
- (해결) 콜백을 약한 참조(weak reference)로 저장 -> GC가 수거
- ex. `WeakHashMap`의 키로 저장
핵심 정리
- 메모리 누수: 겉으로 잘 드러나지 X
- 철저한 코드 리뷰 or 힙 프로파일러 같은 디버깅 도구 필요할 수 있음
- => 예방이 중요
728x90
반응형