Java

[Effective Java] 2-7. 객체 생성과 파괴: 다 쓴 객체의 참조를 해제하라

noahkim_ 2024. 12. 27. 03:55

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.


1. 자바의 메모리 관리

  • GC를 통해 메모리를 자동으로 관리
  • 모든 상황에서 GC가 적절히 회수하도록 보장할 수 없음
  • 개발자가 메모리 누수를 방지하기 위한 추가 조치를 취해야 함

 

2. 메모리 누수 발생 사례

Stack

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()
    • 다 쓴 객체 참조를 제거하지 않음
    • GC는 스택 객체 내부의 배열을 해석하지 못함
      • 스택에서 사용되는 원소와 사용되지 않는 원소를 구분할 수 없음

 

해결책
public Object pop() {
    if (size == 0) throw new EmptyStackException();
		
    Object result = elements[--size];
    elements[size] = null; // 다 쓴 참조 해제
    
    return result;
}
  • 다 쓴 참조를 명시적으로 null 처리하기

 

캐시

  • 객체 참조를 캐시에 저장한 뒤, 사용되지 않는 참조를 제거해야 함

 

해결 방법
Map<Object, Object> cache = new WeakHashMap<>();
cache.put(key, value);
// key가 더 이상 참조되지 않으면, 자동으로 삭제
  •  WeakHashMap
    • 약한 참조를 기반으로 객체 관리
    • 한번 참조된 객체가 더 이상 사용되지 않으면 GC가 자동으로 제거

 

Map<Object, Object> cache = new LinkedHashMap<>(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        return size() > 100; // 캐시 크기가 100을 넘으면 가장 오래된 항목 제거
    }
};
  • LinkedHashMap
    • 유효 기간이 있는 캐시 자료구조
    • 오래된 항목을 제거하는 캐시 정책 구현 가능
    • removeEldestEntry 메서드 오버라이딩