BE/Java

[Effective Java] 아이템 7. 다 쓴 객체 참조를 해제하라

멍목 2022. 9. 5. 23:20
반응형

EFFECTIVE JAVA(이펙티브 자바)

 

 

 

 

 

 

 

이 포스팅에서 작성하는 내용은 EFFECTIVE JAVA(이펙티브자바) 에서 발췌하였습니다.

 


아이템 7. 다 쓴 객체 참조를 해제하라

 

C/C++ 과는 다르게 Java는 GC(Garbage Collector)를 갖추고 있기 때문에 다 쓴 객체를 알아서 회수한다.

하지만, GC가 있다고 해서 메모리 관리에 신경쓰지 않아도 되는 것은 아니다.

 

 

1. Stack

스택을 간단히 구현하면 아래와 같다.

 

1) 메모리 누수가 발생하는 코드

  • 기능적으로는 전혀 문제가 없음
  • 메모리 누수가 발생함
  • 스택이 커졌다가 줄어들었을 때, 스택에서 꺼내진 객체들을 GC가 회수하지 않음
    • 스택에서 꺼내진 객체들은 스택에서 여전히 해당 객체들을 바라보고 있기 때문 (이를 '다 쓴 참조(obsolete reference)' 라고 함)
    • 다 쓴 참조(obsolete reference) : 문자 그대로 앞으로 다시 쓰지 않을 참조
  • 객체 참조를 하나 살려두면 그 객체가 참조하는 모든 객체(또 그 객체들이 참조하는 연관된 객체들 모두)를 회수할 수 없다. → 몇 개의 객체가 매우 많은 객체를 참조한다면 회수를 못하여 성능이 안좋아질 수 있다.
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CPAPCITY = 16;
    
    public statk() {
        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];
    }
    
    // 원소를 위한 공간을 확보하는 메서드
    // 배열의 크기를 늘려야 할 때마다 약 2배 씩 늘림
    private void ensureCapacity() {
        if(elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

 

 

2) 위의 코드에서 pop() 메서드를 보완한 코드

  • pop()으로 객체를 반환해주면서 null로 참조를 해제
public class Stack {
    ...
    
    public Object pop() {
        if(size == 0) {
            throw new EmptyStackException();
            
        Object data = elements[--size];	
        elements[size] = null;				// null 을 넣어줌으로써 객체 참조 해제
        
        return data;
    }
    
    ...
}

 

 

 

모든 객체를 다 쓰자마자 일일이 null 처리를 해야할 까?

결론은 '객체 참조를 null 처리하는 일은 예외적인 경우여야 한다.'

다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scper) 밖으로 밀어내면 된다.

 

 

* null 처리를 해야하는 경우

스택 클래스의 경우 GC가 아닌 자기 메모리를 직접 관리하기 때문에, 프로그래머가 null 처리를 하여 해당 객체를 더 이상 쓰지 않겠다고 GC에 알려야 한다.

즉, 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 한다. 

 

 

* 캐시 사용시에도 조심해야 한다.

 

캐시 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시가 필요한 상황이라면 WeakHashMap을 사용하면 좋다.
(위의 상황에서만 유용)

 

 

 

* 리스너, 콜백도 조심해야 한다.

클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면 콜백은 계속 쌓여간다.

→ 콜백을 약한 참조로 저장하면 GC가 즉시 수거 (ex : WeakHashMap에 키로 저장)

 

 

 

정리

1. 자기 메모리르 직접 관리하는 클래스에서는 다 쓴 객체의 참조를 해제하자.

2. 캐시, 리스너, 콜백을 사용할 때 주의하자.

 

반응형