ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Item 7. 다 쓴 객체 참조를 해제하라
    Book/Effective Java 3E 2022. 10. 12. 22:07
    반응형

      아래 코드는 스택을 간단히 구현한 코드입니다.

    package effectivejava.chapter2.item7;
    import java.util.*;
    
    // 코드 7-1 메모리 누수가 일어나는 위치는 어디인가?
    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);
        }
    
    //    // 코드 7-2 제대로 구현한 pop 메서드
    //    public Object pop() {
    //        if (size == 0)
    //            throw new EmptyStackException();
    //        Object result = elements[--size];
    //        elements[size] = null; // 다 쓴 참조 해제
    //        return result;
    //    }
    
        public static void main(String[] args) {
            Stack stack = new Stack();
            for (String arg : args)
                stack.push(arg);
    
            while (true)
                System.err.println(stack.pop());
        }
    }

      이 코드에서는 스택이 커졌다가 줄어들었을 때 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않습니다. 해법은 코드 7-2처럼 해당 참조를 다 썼을 때 null 처리(참조 해제)하면 됩니다(스택 클래스에서 각 원소의 참조가 더 이상 필요 없어지는 시점은 스택에서 꺼내질 때 입니다). 다 쓴 참조를 null 처리하면 다른 이점도 따라옵니다. 만약 null 처리한 참조를 실수로 사용하려 하면 프로그램은 즉시 NullPointerException을 던지며 종료됩니다.

     

      객체 참조를 null 처리하는 일은 예외적인 경우여야 합니다. 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것입니다. 변수의 범위를 최소가 되게 정의했다면(Item 57) 이 일은 자연스럽게 이뤄집니다.

     

      이 스택은 elements 배열로 저장소 풀을 만들어 원소들을 관리합니다. 배열의 활성 영역(인덱스가 size보다 작은 원소들)에 속한 원소들이 사용되고 비활성 영역은 쓰이지 않습니다. 문제는 가비지 컬렉터는 이 사실을 알 길이 없다는 데 있습니다. 그러므로 프로그래머는 비활성 영역이 되는 순간 null 처리해서 해당 객체를 더는 쓰지 않을 것임을 가비지 컬렉터에 알려야 합니다.

     

      일반적으로 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 합니다. 원소를 다 사용한 즉시 그 원소가 참조한 객체들을 다 null 처리해줘야 합니다.

     

      캐시, 리스너(listener) 혹은 콜백(callback) 역시 메모리 누수를 일으키는 주범입니다.

     

    메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다.

    이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견되기도 한다.

    그래서 이런 종류의 문제는 예방법을 익혀두는 것이 매우 중요하다.

     

    [참고 정보]

    이펙티브 자바 Effective Java 3/E 도서 [조슈아 블로크 ]

    이펙티브 자바 깃허브 저장소

    <이펙티브 자바, 3판> 번역 용어 해설

    백기선 님의 [이펙티브 자바] 강의

    백기선 님 깃허브 저장소

    반응형

    댓글

Designed by Tistory.