[EffectiveJava] Item 7. 다 쓴 객체 참조를 해제하라

2025. 7. 1. 19:29·Backend/Java

메모리 누수를 주의해야 한다.

자바에서는 가비지 컬렉터가 다쓴 객체를 알아서 회수해간다 하지만 그렇다고해서 메모리 관리에 신경쓰지 않으면 안된다. 메모리 누수가 발생하는 프로그램을 오래 실행하다보면 점차 가비지 컬렉션 활동과 메모리 사용량이 늘어나 결국 성능이 저하되거나 메모리초과 오류(OufOfMemoryError) 가 발생할 수 있다.

[메모리 누수가 일어나는 스택 구현]

public class StackMemoryLeak {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity(e);
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
// pop을 하지만 elements는 초기화되지 못하고 elements[size]를 여전히 참조하게 됨
        return elements[size--];
    }

    private void ensureCapacity(final Object e) {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}
  • 위와 같은 스택은 elements 배열이 사라지지 않고 메모리에 계속 누적되며 객체의 참조를 가지고 있게 되므로 가비지 컬렉터가 작동하지 못한다.
    • 스택이 그 객체들의 다 쓴 참조(obsolete reference) 를 여전히 가지고 있게 된다는 뜻
    • elements 배열의 활성 영역밖의 참조들이 모두 다 쓴 참조에 해당된다.
      • 활성 영역 :인덱스가 size보다 작은 원소들을 뜻함
  • 객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐만 아니라 그 객체가 참조하는 모든 객체를 회수해가지 못한다

 

메모리 누수 방지 방법

  • 가장 간단한 방법은 해당 참조를 다 사용했을 때 변수를 null로 선언(참조 해제) 해버리는 것이다
  • 그럼 더 이상 Heap 영역에 저장된 객체에 대한 참조가 존재하지 않기 때문에 가비지 컬렉터가 작동할 수 있다
    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        Object result = elements[--size];
        elements[size] = null;

        return result;
    }

허나 모든 객체를 다 쓰자마자 null 처리하는 것은 별로 바람직하지 않다.

객체 참조를 null 처리하는 일은 예외적인 경우여야 하며 가장 마지막에 고려되어야한다.

 

일반적으로 다 쓴 참조를 해제하는 가장 좋은 방법은 해당 변수를 scope (유효 범위) 밖으로 밀어내는 것이다.

 

 

그렇다면 null처리는 언제해야 할까?

  • 예시로 든 Stack클래스처럼 자기 메모리를 직접 관리하는 경우에 메모리 누수에 취약해진다.
    • 객체 자체가 아니라 객체 참조를 담는 elements 배열로 메모리를 관리한다
  • 이러한 경우 프로그래머는 비활성 영역이 되는 순간 null 처리를 통해 해당 객체가 더 이상 쓰이지 않는다는 사실을 가비지 컬렉터에게 알려주어야 한다.

추가적으로 주의해야 할 메모리 누수 사례

1. 캐시 메모리 누수

객체 참조를 캐시에 넣어두고 이를 방치하는 일이 자주 발생한다. 이를 해결하는 방법에는 여러가지가 있다.

  • WeakHashMap 을 사용해 캐시 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시를 사용하는 방식.
    더보기
    WeakHashMap[WeakHashMap]

    일반적인 HashMap의 경우 Key와 Value가 put되면 사용여부에 관계없이 해당 내용은 제거되지 않는다. 하지만 weakHashMap의 경우 Key에 해당하는 어떤 객체가 null이 되면 해당 객체를 key로 하는 HashMap의 Element도 더이상 사용하지 않는다고 판단되어 자동으로 제거되어 버린다.
  • ScheduledThreadPoolExecutor 같은 백그라운드 스레드를 활용해 엔트리를 청소하는 방식.
  • LinkedHashMap 의 removeEldesEntry 메서드를 활용해 엔트리를 청소하는 방식.
  • java.lang.ref 패키지를 이용해 더 복잡한 캐시를 만드는 방법.

 

2. 리스너(listener) 혹은 콜백(callback)

클라이언트가 콜백을 등록만하고 명확히 해지하지 않는다면, 콜백은 계속 쌓이게 된다.
이럴 때 콜백을 약한 참조(weak reference)로 저장하면 가비지 컬렉터 가 즉시 수거해 간다.

ex) WeakHashMap에 키로 저장

 


정리

  1. 메모리 누수는 겉으로 잘 드러나지 않기 때문에 이런 종류의 문제는 예방법을 익혀두는것이 중요하다. 그렇지 않으면 디스크 페이징이나 OutOfMemoryError를 일으켜 프로그램이 예기치 않게 종료될 수 있다.

  2. 메모리누수를 방지하는 방법으로 다 쓴 참조 객체를 null처리 해주는 방법이 있다. 하지만, 사실 모든 객체를 다 사용 후 null처리하는것은 별로 바람직하지 않다.
    • 위에서 본 스택의 경우 스택에서 꺼내진 객체들에 대한 다 쓴 참조를 여전히 스택이 가지고 있다. 꺼내진 객체들은 비활성영역에 해당하는 객체들인데 가비지 컬렉터는 이런 비활성영역에 대한 객체들을 회수하지 못한다. 이처럼 자기 메모리를 직접 관리하는 클래스의 경우 참조한 객체를 사용 후 null처리를 통해서 객체가 사용하지 않는다는 사실을 가비지 컬렉터에게 알리고 메모리 누수를 방지하자.

  3. 다 사용한 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수의 범위를 최소한으로 지정하여 사용 후 유효범위 밖으로 자연스레 밀려나게 하는 것이다.

'Backend > Java' 카테고리의 다른 글

[Java] 정렬 알고리즘 정리  (1) 2025.07.02
[EffectiveJava] Item 14. Comparable을 구현할지 고려하라  (1) 2025.07.01
[EffectiveJava] Item 55. 옵셔널 반환은 신중히 하라  (1) 2025.07.01
[EffectiveJava] item 61. 박싱된 기본 타입보다는 기본 타입을 사용하라  (0) 2025.07.01
[EffectiveJava] item 64. 객체는 클래스가 아닌 인터페이스로 참조하라  (0) 2025.07.01
'Backend/Java' 카테고리의 다른 글
  • [EffectiveJava] Item 14. Comparable을 구현할지 고려하라
  • [EffectiveJava] Item 55. 옵셔널 반환은 신중히 하라
  • [EffectiveJava] item 61. 박싱된 기본 타입보다는 기본 타입을 사용하라
  • [EffectiveJava] item 64. 객체는 클래스가 아닌 인터페이스로 참조하라
devoks
devoks
꾸준히 작성해보자!
  • devoks
    ok's 개발 블로그
    devoks
  • 전체
    오늘
    어제
    • 분류 전체보기 (112) N
      • Backend (15)
        • SpringBoot (0)
        • Java (15)
      • Cs (18) N
      • Infra (0)
        • AWS (0)
        • Docker (0)
      • CodingTest (79)
        • Programmers (79)
  • 링크

    • My GitHub
  • 인기 글

  • 태그

    json
    programmers
    StringTokenizer
    BufferedWriter
    codingtest
    effectivejava
    java
    switch
    BufferedReader
    CS
  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
devoks
[EffectiveJava] Item 7. 다 쓴 객체 참조를 해제하라
상단으로

티스토리툴바