[Effective Java] item7 - 다 쓴 객체 참조를 해제하라

신민철·2023년 4월 12일
0

Effective Java

목록 보기
7/23
post-thumbnail

JAVA에는 GC(Garbage Collector)가 있어서 메모리 관리에 더 이상 신경을 쓰지 않아도 된다.

맞을까? 정답은 아니다 이다. 나는 자바를 배우면서 여태까지 맞다라고 생각했는데 흥미롭다.

아래의 코드를 살펴보자.

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와 메모리 사용량이 늘어나 성능이 저하된다.

심할 때는 디스크 페이징이나 OutOfMemoryError를 발생시켜 프로그램이 종료되기도 한다.

그리고 다 쓴 객체에서 문제가 발생하게 된다. GC는 다 쓴 해당 객체를 버리지 않는다. 객체의 다 쓴 참조(obsolete reference)를 계속 갖고 있기 때문이다. 스택의 크기가 줄어도 늘어나도 계속 갖고 있게 되는 것이다.

여기서 해결 방법은 다 쓴 객체의 레퍼런스를 null로 변경해주면 되는 것이다.

public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
	elements[size] = null;
	return result;
}

전 코드에서는 다 쓴 레퍼런스에 접근하면 어떠한 Exception도 나오지 않았는데 여기에서는 null로 처리하므로 다 쓴 것에 접근했을 때 NullPointerException이 발생하므로 부수적인 이점이 따라오는 것이다.

의도하지 않았지만 프로그램의 오류를 처리한 것과 마찬가지다.

방금 생각했을 때 “그럼 이제 앞으로 안 쓰는 객체의 레퍼런스는 모두 null로 처리해야겠다.” 라고 생각이 들 것이다.

하지만 다행히도 그럴 필요도 없고 불필요하게 코드만 지저분해질 뿐이다. 이런 일은 예외적인 경우여야 한다.

전의 예시를 살펴보면 Stack 클래스는 자기 자신의 메모리를 직접 관리한다. 이런 경우에는 사용한 즉시 null 처리를 해줘야 한다. 이런 것이 예외적인 경우이다.




캐시 또한 메모리 누수의 주범이다!! 가끔 객체 참조를 캐시에 넣어두고 까먹고 있다 객체를 다 쓰고도 캐시를 유지하고 있으면 문제가 생긴다.. 만약 캐시 외부에서 를 참조하는 동안만 캐시가 필요하다면 그냥 WeakHashMap을 사용하면 된다!

사실 캐시를 사용하게 되면 유효기간을 파악하기 어려운데 그래서 일반적으로 사용하지 않는 엔트리들은 주기적이거나 아니면 어떤 이벤트마다 청소를 해줘야 한다.

백그라운드 쓰레드를 사용하거나 캐시에 새 엔트리가 등록될 때 제거하는 방법이 있다.

LinkedHashMap은 removeEldeestEntry 메소드를 사용하여 후자의 방식으로 처리하게 된다.



다음 메모리 누수의 주범은 리스너(listener)와 콜백(callback)이다!

콜백을 등록만 하고 해지하지 않으면 계속 쌓이게 된다.

이를 방지하기 위해선 WeakHashMap에 Weak Reference로 등록하게 되면 방지가 되게 된다.

핵심 정리
메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다. 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같은 디버깅 도구를 동원해야만 발견하기도 하는데 위의 세가지의 주범들을 잘 기억하고 미리 예방하는 방법을 익히자!

0개의 댓글