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

박상준·2024년 5월 22일
0

이펙티브 자바

목록 보기
7/19
post-custom-banner

개요

  • C 나 C++ 같은 언어에서는 메모리를 직접 관리합니다.
  • 다만, Java 같은 언어는 GC 가 존재하기에, 메모리를 관리하지 않아도 자동으로 처리를 해줍니다.
  • GC 에서 컨트롤하지 못하는 인스턴스 등에 대해서는 메모리 누수(memory leak) 이 발생할 수 있으며,
    1. 성능 저하
    2. OOM(Out of Memory) 같은 심각한 문제를 일으킬 수 있음.

스택 구현부

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);
    }
}
  • Stack 자료구조를 구현한 클래스 예시입니다.
  • 구조분해
    1. 자료구조 선언
      1. elements : 어떤 자료형이든 담을 수 있도록 Object[] 로 배열이 선언되어 있습니다.
      2. size : 배열의 크기에 대해 직접적으로 관리해줄 수 있도록 사이즈가 선언되어 있습니다.
      3. DEFAULT_INITIAL_CAPACITY ( 상수 ) :배열 초기 사이즈입니다.
    2. 생성자
      1. 초기 사이즈 설정에 맞게 내부 배열을 설정합니다
    3. push
      1. ensureCapacity()
        1. 현재 배열에 어느정도의 데이터가 들어간지 체크하고,
        2. 만약 현재 배열이 가득찼다고 판단되는 경우, ele 할당 사이즈를 2배 +1 크기만큼 증대합니다.
      2. 그리고나서 현재 배열 안의 데이터를 삽입
    4. pop
      • size가 0 인 경우
        • pop 을 수행할 수 없기에, 에러를 발생시킵니다.
      • size 0 이 아닌 경우 원소제거 후 size 컨트롤

메모리 누수가 발생하는 부분

  • pop 메서드에서 메모리 누수가 발생합니다.
  • 스택이 커졌다가 줄어들떄 스택에서 꺼내진 객체들을 GC가 회수하지 못합니다.
    • 개발자는 명시적으로 해당 원소가 참조되지 않는다는 것을 알 수 있지만,
    • 개발 프로그램을 ele 배열의 활성 영역 밖의 참조를 하고 있습니다.

해결방안

  • 다 쓴 객체 참조를 명시적으로 해제하여 메모리 누수를 방지할 수 있습니다.
  • pop 메서드에서 다 쓴 객체 참조를 null 로 설정하여 GC 가 이를 회수할 수 있게 설정

메모리 누수를 방지하는 예시

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // 다 쓴 객체 참조 해제
        return result;
    }
  1. 다 쓴 객체를 참조 해제한다.
    1. elements 에서 size 부분에 대해서 참조 해제를 null 로 수행함
    2. GC가 해당 부분을 회수할 수 있도록 한다.
  2. 변수의 유효범위를 최소화한다
    1. 잘못된 예시

      public class Example {
          public void process() {
              // 잘못된 예시: 변수를 메서드 시작 부분에 선언
              int result;
              for (int i = 0; i < 10; i++) {
                  result = compute(i);
                  System.out.println(result);
              }
          }
      
          private int compute(int value) {
              return value * 2;
          }
      }
      • 에서 result 는 for 문 안의 하나의 사이클안에서 돌아가는 범위를 가져도 되는 지역변수로 사용되어도 문제없다,
      • 불필요하게 범위가 넓은 변수인 것이다.
      • 그래서…
      for (int i = 0; i < 10; i++) {
          // 올바른 예시: 변수를 필요한 범위 내에서 선언
          int result = compute(i);
          System.out.println(result);
      }
      • 이런식으로 필요한 범위 내에서 선언하는 것이 중요하다.
  3. 캐시 관리
    • 캐시를 사용시에 다 쓴 엔트리를 주기적으로 청소하거나, WeakHashMap 을 사용해 자동으로 제거되도록 할 수 있음.
      • WeakHashMap
        • 정의
          • 키(key) 에 대하여 약한 참조(weak reference) 를 사용하여, 키가 더 이상 외부에서 참조되지 않는 경우 GC 가 해당 엔트리를 자동으로 제거할 수 있도록 한다.
        • 약한 참조
          • expungeStaleEntries
                private void expungeStaleEntries() {
                    for (Object x; (x = queue.poll()) != null; ) {
                        synchronized (queue) {
                            @SuppressWarnings("unchecked")
                                Entry<K,V> e = (Entry<K,V>) x;
                            int i = indexFor(e.hash, table.length);
            
                            Entry<K,V> prev = table[i];
                            Entry<K,V> p = prev;
                            while (p != null) {
                                Entry<K,V> next = p.next;
                                if (p == e) {
                                    if (prev == e)
                                        table[i] = next;
                                    else
                                        prev.next = next;
                                    // Must not null out e.next;
                                    // stale entries may be in use by a HashIterator
                                    e.value = null; // Help GC
                                    size--;
                                    break;
                                }
                                prev = p;
                                p = next;
                            }
                        }
                    }
                }
            
            • e.value = null; 에서 gc 를 동작시킴.
profile
이전 블로그 : https://oth3410.tistory.com/
post-custom-banner

0개의 댓글