Java/이펙티브 자바(Effective Java)

[이펙티브 자바(Effective Java)][객체 생성과 파괴] - 7. 다 쓴 객체 참조를 해제하라

얄루몬 2022. 4. 11. 18:17

📖본 포스팅은 'Effective java - 조슈아' 님의 책를 보고 작성되었습니다.


[자바의 가비지 컬렉터?]

[가비지 컬렉터(가비지 컬렉션)]
쓰레기 수집(garbage collection 가비지 컬렉션, GC)은 메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능이다. 영어를 그대로 읽어 가비지 컬렉션이라 부르기도 한다.
📌출처: https://ko.wikipedia.org/wiki/%EC%93%B0%EB%A0%88%EA%B8%B0_%EC%88%98%EC%A7%91_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)
  • c언어 c++언어의 경우엔 메모리를 직접 관리해야 하기 때문에 자바처럼 가비지 컬렉터를 갖춘 언어로 넘어오면 프로그래머의 삶이 평안해진다.
  • 자바의 경우엔 다 쓴 객체를 가비지 컬렉터가 알아서 회수해간다.
  • 위의 사실만 본다면 메모리 관리를 신경쓰지 않아도 될것 같지만 그렇지 않다. 자바 역시도 다 쓴 객체가 있다면 참조를 해제해주어야 메모리 낭비를 막을 수 있다.

 

[잡기 힘든 메모리 누수]

package hello.springmvc;

import java.util.Arrays;
import java.util.EmptyStackException;

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

[메모리 누수를 막을 방법은?]

  • 해당 참조를 다 썼을 때 null처리(=참조 해제)를 해주면 된다.
    • 위의 코드의 경우 null 처리(=참조 해제)를 해주는 타이밍은 스택 클래스에서 각 원소의 참조가 더 이상 필요 없어지는 시점이 스택에서 꺼내질 때고 그때 참조 해제를 해주면 된다.
    public Object pop(){
        if (size==0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }

 

 

  • 스택에서 해당 값을 빼게 되면 더이상 그 객체는 필요하지 않기 때문에 null 처리(=참조 해제)를 해준 뒤 새로운 값을 돌려주면 된다.

[그러나 객체 참조를 null 처리하는 예외적인 경우어야 한다.]

  • 다쓴 객체 참조를 null처리 하는 것보다 그 참조 변수를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것이 가장 좋은 객체 참조 해제 방법이다. 
  • 변수의 범위를 최소가 되게 정의했다면 이 일은 매우 자연스러워진다.(아이템 57에서 이 이야기를 다룸)
  • null 처리를 하는 예외적인 경우는?
    • Stack과 같은 자료구조는 메모리를 본인이 직접 관리하기 때문에 메모리 누수에 굉장히 취약하다. 이는 결국 활성 영역과 비활성 영역에 관해서 프로그래머만 아는 사실이기에 이를 관리하기 위해서는 다 쓴 객체 참조를 null 처리를 이용해서 다시는 쓰지 않음을 가비지 컬렉터에 직접 알려주어야 한다.
    • 즉, 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 한다!!!

[메모리 누수를 일으키는 주범들?]

  • 캐시
  • 리스너(listener) 혹은 콜백(callback)
    • 콜백은 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다.
    • WeakHashMap에 키로 저장하는 등의 방법이 있다.

 

[핵심]

  • 메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 경우도 많다.
  • 이런 누수는 철저한 코드 리뷰, 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 하기에 이런 종유의 문제는 예방법을 익혀두는 것이 매우 중요하다.