티스토리 뷰

반응형

 

자바의 가비지 컬렉터는 알아서 다 쓴 객체를 회수해간다.

그럼에도 메모리 관리에 신경써야한다.

public class Stack {
    private static final int DEFAULT_INITAL_CAPACITY = 16;

    private Obejct[] elements;
    private int size = 0;

    public Stack() {
        elements = new Object[DEFAULT_INITAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++];
    }

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

위 코드는 스택을 구현한 코드다.

이 코드에는 메모리 누수가 일어나 오래 실행하면 가비지 컬렉션 활동과 메모리 사용량이 늘어나 성능이 저하된다. (디스크 페이징 / OutofMemoryError 위험)

pop() 시에 꺼내진 객체들을 가비지 컬렉터가 회수하는게 아니라 그저 인덱스만 움직이기 때문에 메모리 누수가 일어난다.

즉, 스택이 다 쓴 참조(obsolete reference)를 여전히 갖고 있는 문제가 발생한다.

이렇게 의도치 않은 메모리 누수를 찾기란 어렵다.

가비지 컬렉터는 객체 참조 하나를 살려두면 그 객체 뿐아니라 그 객체가 참조하는 모든 객체를 회수하지 못한다.

→ 해당 참조를 다 썼을 때 null 처리를 하자.

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

pop()을 제대로 구현하면 위와 같다. 다 쓴 객체는 null 처리를 해주었다.

그런데 객체 참조를 null 처리하는 일은 예외적인 상황이어야한다.

다 쓴 참조를 해제하는 가장 좋은 방법은 그 변수를 유효범위 밖으로 밀어내는 것이다.

그렇다면 언제 null 처리를 해줘야할까?

 

Stack 클래스는 자기 메모리를 직접 관리하기 때문에 null 처리를 해야하는 것이다.

스택은 elements 배열로 저장소 풀을 만들어서 원소를 직접 관리한다.

배열의 활성 영역에 속한 원소들은 사용되지만 비활성 영역은 쓰이지 않는다.

하지만 가비지 컬렉터는 이러한 사실을 알리가 없다.

그래서 가비지 컬렉터에게 알리기 위해서 null 처리를 해줘야하는 것이다.

 

 

 

 

메모리 누수의 주범

1) 메모리를 직접 관리하는 클래스

자기 메모리를 직접 관리하는 클래스라면 메모리 누수에 주의해야한다.

→ 다 사용한 원소는 즉시 null 처리해주어야한다.

 

2) 캐시

객체 참조를 캐시에 넣어놓고서 다 쓴 후에도 그냥 놔두면 메모리 누수가 생긴다.

→ 캐시 외부에서 키를 참조하는 동안만 엔트리가 살아 있는 캐시가 필요한 상황이라면 WeakHashMap 을 사용해 캐시를 만들자. (다 쓴 엔트리는 즉시 자동 제거)

→ 엔트리의 유효기간을 정확히 알기 어려워 시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 사용한다. 이 방식에서는 쓰지 않는 엔트리를 청소해줘야한다.

  • ScheduledThreadPoolExcutor 같은 백그라운드 스레드를 활용
  • 캐시에 새 엔트리를 추가할때 부수 작업으로 청소를 수행
    ex) LinkedHashMap은 removeEldestEntry 메서드를 사용

 

3) listener 혹은 callback

클라이언트가 콜백을 등록만 하고 해지하지 않는다면, 계속 쌓여갈 것이다.

→ 콜백을 약한 참조(weak reference)로 저장하면 가비지 컬렉터가 즉시 수거해간다. ex) WeakHashMap에 키로 저장

 

 

 

 

결론

메모리 누수는 잘 드러나지 않기 때문에 예방법을 익혀두어 예방하는 것이 매우 중요하다.

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday