Book

[Effective Java] Item 7. 다 쓴 객체 참조를 해제하라

Jordy-torvalds 2021. 10. 6. 10:05
반응형

C, C++처럼 메모리를 직접 관리해야 하는 언어와 달리 자바는 가비지 컬렉터를 갖춘 언어이다. 하지만 그럼에도 불구하고 메모리 누수가 발생한다. 아래 코드가 대표적인 메모리 누수가 일어나는 클래스다.

public class Stack {
    private Object[] elements ;
    private int size = 0;

    // ...

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

    // ...

}

Stack 이란 자료구조는 원래 pop을 하면서 해당 데이터가 내보내는데, 위 코드에서는 size 변수의 값을 1 내림으로써 pop한 데이터를 남긴다.

public class Stack {
    private Object[] elements ;
    private int size = 0;

    // ...

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

    // ...

}

위와 같은 코드로 개선하면 더이상 데이터가 남지 않게 된다. 하지만 위 처럼 객체 참조를 null 처리하는 일은 예외적인 경우여야 한다.

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

캐시 역시 메모리 누수를 일이키는 주범이다.

객체 참조를 캐시에 넣고 나서, 이 사실을 까맣게 잊은 채 그 객체를 다 쓴 뒤로도 한참을 놔두는 일을 자주 접할 수 있다.

메모리 누수의 세 번째 주범은 바로 리스너 혹은 콜백이라 부르는 것이다.

클라이언트(클래스를 참조하는 클래스)가 콜백을 등록만 하고 명확히 해지하지 않는다면, 뭔가 조치해주지 않는한 계속 쌓여갈 것이다.

이럴때 약한참조로 저장하면 가비지 컬렉터가 즉시 수거해 간다, 예를 들면, WeakHashMap에 키로 저장하면 된다. 그냥 HashMap도 가능하다.

Reference 1. 가비지 컬랙션 대상

가장 기본적인 가비지 컬랙션의 대상은 아래와 같다.

  • 객체 참조 변수가 null로 초기화된 객체

아래와 같이 null을 할당해주면 jordyBag에 저장되어 있던 스트링 객체 참조가 모두 null로 초기화 되므로 jordyBag은 사라지게 된다.

하지만 놀랍게도 이펙티브 자바란 문자열이 담긴 문자열은 사라지지 않고 남아있었는지 동일한 해쉬코드가 출력 되었다.

testStr 변수의 생성 위치를 "칫솔" 을 add하는 곳 밑으로 옮겨도 결과는 동일하게 나왔다. 스트링 특성상 할당된 문자열을 상수 형태로 값을 저장하기 때문에 이와같은 결과가 나오는 것으로 생각된다.

스트링을 대신해 스트링버퍼를 넣으면 해쉬코드가 변경되는 것을 확인할 수 있었다.

ArrayList jordyBag = new ArrayList();
jordyBag.add("이펙티브 자바");
jordyBag.add("텀블러");
jordyBag.add("칫솔");
System.out.println("jordyBag.get(0).hashcode()"+jordyBag.get(0).hashCode() );
jordyBag = null;

System.gc();
String testStr = "이펙티브 자바";
System.out.println("testStr.hashcode()"+testStr.hashCode() );

jordyBag.get(0).hashcode()-151120765
testStr.hashcode()-151120765
  • Scope를 통한 자동 할당 해제

보통은 변수 선언과 동시에 초기화를 사용한다.

그 변수에 대한 scope가 종료되는 순간 reference가 해제되어 가비지 컬렉션 대상이 된다.

반응형