본문 바로가기
개인 공부/Java (이펙티브 자바)

[이펙티브 자바] 아이템 7 : 다 쓴 객체 참조를 해제하라

by 희조당 2023. 1. 31.
728x90

🎯 학습 목표

  • GC(Garbage Collector)의 동작
  • 메모리를 관리하는 객체

📌 객체 참조를 해제하라!

C 계열 개발자가 부러워하는 GC는 개발자가 직접적으로 메모리를 관리하지 않아도 되게 해준다.

하지만 GC가 있다고 절대적으로 메모리 누수가 발생하지 않는 것은 아니다.

 

책에는 다음과 같은 예시를 제공한다.

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);
    }
}

그냥 봤을 때는 이상이 전혀 없지만 메모리 누수가 발생하고 있다. 

스택에서 꺼내진 객체들을 더 이상 사용되지 않더라도 (다 쓴 참조) GC가 회수하지 않는다.

GC는 살아있는 참조가 하나라도 있으면 그 참조와 연관된 모든 메모리를 회수하지 않는다.

🤔 왜 회수하지 못할까?

추적(Tracing) 기반으로 작동하는 오늘날 JAVA의 GC는 항상 접근 가능한 메모리(root)를 기반으로 동작한다.

root를 기반으로 도달할 수 있는 모든 객체의 참조를 찾아낸다. 여기서 도달할 수 없는 객체의 참조가 회수 대상이다.

GC가 객체를 회수하기 위해선 해당 객체가 참조하는 모든 객체도 회수해야 하고 이 과정은 재귀적으로 이루어진다.

따라서, 위의 스택이 살아있는 한 메모리 누수가 계속 발생할 것이고 나아가 프로그램 자체를 종료시킬 수도 있다.

😉 해결 방법

간단하게 다 쓴 참조를 null 처리해서 해결할 수도 있다.

public Object pop() {
    if (size == 0) {
        throw new EmptyStackException();
    }
    Object result = elements[--size];
    elements[size] = null; // 다 쓴 참조 해제
    return elements[--size];
}

하지만 무조건적으로 null을 넣어주는 것이 좋은 것만은 아니기 때문에 이와 같이 특별한 상황에만 써야 한다.

즉, 자기 메모리를 직접 관리하는 객체에서 다 쓴 메모리의 참조를 해제해야 한다.

😎 그 외의 메모리 누수

객체 참조를 캐시에 넣어놓는 경우에도 캐시의 참조가 해제되지 않는다면 메모리가 줄줄 샌다.

또한, 리스너나 콜백에서도 누수가 발생할 수 있다.

😋 정리

결국 GC의 동작원리를 이해하고, 스스로 메모리를 관리하는 객체에서 메모리 누수가 발생할 수 있다는 것만 기억하자!


-Reference

https://medium.com/@joongwon/jvm-garbage-collection-algorithms-3869b7b0aa6f

 

😋 지극히 개인적인 블로그지만 훈수와 조언은 제 성장에 도움이 됩니다 😋

댓글