메모리 누수는 잘 드러나지 않으므로 아래 경우를 익히고 예방하는 것이 중요하다.
1. 스택 구현 코드의 메모리 누수
아래 코드를 보면 size를 통해 활성 영역을 (스택에 있는 값) 조절하고 있다.
pop() 메서드를 보면 size--만 할 뿐, 해당 null 처리하지 않는다.
아래에서 스택에서 꺼내진 객체는 가비지 컬렉터가 회수하지 않는다.
스택이 꺼낸 객체에 대한 참조를 유지하고 있기 때문이다.
이는 잠재적으로 성능에 안좋은 영향을 줄 수 있다.
public class Stack{
private Object[] elements;
private int size = 0;
priavte 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 EmptyStackExcpetion();
}
return elements[--size];
}
// 원소 공간 확보용
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements, 2 * size+1);
}
}
}
➡️ 필요없는 값은 null 처리를 해서 참조를 해제해야한다.
개선된 pop 메서드
public Object pop(){
if(size == 0){
throw new EmptyStackExcpetion();
}
Object result = elements[--size];
elements[size] = null; // 참조 해제
return result;
}
객체 참조를 null 처리하는 방식은 예외적으로 사용해야한다.
가장 좋은 방법은 참조를 담은 변수를 유효 범위 밖으로 밀어내는 방식이다.
스택에서는 null 처리를 해야하는 이유가 뭘까?
스택은 참조를 담은 elements 배열을 만들어 직접 메모리를 관리한다.
위에서 정리했듯 활성 영역 - 비활성 영역으로 나눠 관리하지만, 가비지 컬렉터는 참조 여부에 따라 객체를 회수한다.
따라서 가비지 컬렉터 입장에서는 두 영역 모두 유효한 객체인 것이다.
⭐️ 스택을 비롯해서 메모리를 직접 관리하는 클래스에서는 메모리 누수에 주의하며 null처리를 해줘야한다.
2. 객체 참조를 캐시에 넣은 경우 메모리 누수
객체 참조를 캐시에 넣고, 객체를 쓴 다음에도 놔두는 경우 메모리 누수가 발생한다.
만약 참조하는 동안만 캐시에 넣고 싶은 상황이라면, 캐시를 WeekHashMap으로 만들자.
1) HashMap
- Key-Value 모두 강한 참조(Strong Reference)
- 키를 캐시에 넣고 외부에서 사용하지 않더라도, HashMap이 키를 참조하고 있기 때문에 가비지 컬렉터가 회수하지 못함
2) WeakHashMap
- 키가 약한 참조(Weak Reference)
- 외부에서 키를 더 이상 참조하지 않으면 자동으로 GC에 의해 제거됨
- 단점: GC가 언제 동작할지 예측할 수 없어서 캐시 만료를 제어하기 어려움
그러나, 캐시 유효기간을 정확히 정의하기는 어렵기 때문에, 캐시 엔트리 가치 (순위)를 떨어뜨리는 방식을 사용한다.
이 방법은 주기적으로 쓰지않는 엔트리를 스케쥴러로 청소해야한다.
3) LinkedHashMap
- LinkedHashMap은 강한 참조를 유지하지만, 특정 조건을 만족하면 엔트리를 자동으로 삭제할 수 있음
- removeEldestEntry()를 오버라이딩하면, LRU(Least Recently Used) 방식으로 오래된 데이터를 삭제할 수 있음
3. 콜백 등록 후 해지하지 않은 경우
클라이언트가 콜백을 등록만하고 해지하지 않으면, 콜백이 쌓여감
이때 콜백을 등록할 때 약한 참조로 저장하면 GC가 즉시 수거해간다.
ex) WeakHashMap에 키로 저장
클라이언트가 등록만하고 해지하지 않는 경우
import java.util.ArrayList;
import java.util.List;
interface EventListener {
void onEvent(String message);
}
class EventPublisher {
private final List<EventListener> listeners = new ArrayList<>();
public void registerListener(EventListener listener) {
listeners.add(listener); // 강한 참조 유지 → 클라이언트가 사라져도 리스너는 계속 남아있음
}
public void notifyListeners(String message) {
for (EventListener listener : listeners) {
listener.onEvent(message);
}
}
}
public class StrongReferenceExample {
public static void main(String[] args) {
EventPublisher publisher = new EventPublisher();
EventListener listener = message -> System.out.println("Received: " + message);
publisher.registerListener(listener); // 리스너 등록
listener = null; // 참조 해제
System.gc(); // GC 강제 호출 (하지만 강한 참조가 남아있어 삭제 안 됨)
publisher.notifyListeners("Hello!"); // 여전히 리스너가 남아 있음
}
}
콜백 등록시 WeekHashMap에 Key값으로 등록하는 경우
gc 호출시 삭제됨
import java.util.Map;
import java.util.WeakHashMap;
interface EventListener {
void onEvent(String message);
}
class EventPublisher {
private final Map<EventListener, Boolean> listeners = new WeakHashMap<>();
public void registerListener(EventListener listener) {
listeners.put(listener, Boolean.TRUE); // WeakHashMap에 추가 (키가 약한 참조)
}
public void notifyListeners(String message) {
for (EventListener listener : listeners.keySet()) {
listener.onEvent(message);
}
}
}
public class WeakReferenceExample {
public static void main(String[] args) {
EventPublisher publisher = new EventPublisher();
EventListener listener = message -> System.out.println("Received: " + message);
publisher.registerListener(listener); // 리스너 등록
listener = null; // 클라이언트가 참조를 해제
System.gc(); // GC 호출 → WeakHashMap에서 자동 삭제됨
publisher.notifyListeners("Hello!"); // 등록된 리스너가 사라졌으므로 호출되지 않음
}
}
'도서 > 이펙티브 자바' 카테고리의 다른 글
| [이펙티브 자바] 9. try-finally 보다는 try-with-resources를 사용하라 (0) | 2025.04.15 |
|---|---|
| [이펙티브 자바] 8. finalizer 와 cleaner 사용을 피하라 (0) | 2025.03.23 |
| [이펙티브 자바] 6. 불필요한 객체 생성을 피하라 (0) | 2025.03.20 |
| [이펙티브 자바] 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2025.03.04 |
| [이펙티브 자바] 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2025.03.03 |