ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 더이상 쓰지 않는 객체 레퍼런스는 없애자 + Weak Reference
    JAVA/Effective java 2021. 1. 2. 20:41

    메모리 직접 관리


    • 자바에 GC (가비지 콜렉터)가 있기 때문에 메모리 관리에 대해 신경 쓰지 않아도 될 거라고 생각하기 쉽지만 그렇지 않다.
    • 다음 코드를 살펴보자.

     

    • 여기서의 size는 index에 역할을 하고 있다.
    • pop()을 보면 내가 넣을 장소가 아닌 뺄 장소로 이동해야 하기 때문에 먼저 --size를 하고 반환한다.
    • 배열은 계속해서 값을 넣기만 하고 없어지지는 않는다.
    • 스택에 계속 쌓다가 많이 빼냈다고 치자, 그래도 스택이 차지하고 있는 메모리는 줄어들지 않는다.

     

     

    • 왜냐면 저 스택의 구현체는 필요없는 객체에 대한 레퍼런스를 그대로 가지고 있기 때문이다.
    • 가용한 범위라는 것은 실제 유의미한 element를 가지고 있는 범위이고 이는 size 보다 작은 부분들이다.
    • 그 값 보다 큰 부분에 있는 값들은 필요 없이 메모리를 차지하고 있는 부분이다. 따라서 다음과 같이 코드를 수정할 수 있다.

    • 실수로 해당 위치에 있는 객체를 다시 꺼내는 경우에 NullPointerException이 발생할 수 있긴 하지만,
    • 그 자리에 있는 객체를 비우지 않고 실수로 잘못된 객체를 돌려주는 것보다는 차라리 괜찮다.
    • 프로그래밍 에러는 언제든지 가능한한 빨리 포착하는 것이 유익하다.

     

     

    • 그렇다고 필요없는 객체를 볼 때마다 null로 설정하는 코드를 작성하지는 말자.
    • 객체를 Null로 설정하는 건 예외적인 상황에서나 하는 것이지 평범한 일이 아니다.
    • 필요 없는 객체 레퍼런스를 정리하는 최선책은 그 레퍼런스를 가리키는 변수를 특정한 범위(스코프) 안에서만 사용하는 것이다.
    • 로컬 변수는 그 영역 넘어가면 쓸모 없어져서 정리되기 때문이다.

     

     

    • 변수를 가능한 가장 최소의 스콥으로 사용하면 자연스럽게 그렇게 될 것이다.
    • 하지만 위에 코드처럼 size라는 멤버 변수와 elements를 쓰는 경우엔 역시 자연스럽게 그렇게 되진 않는다.
    • 따라서 예외적인 상황으로 명시적으로 null로 설정하는 코드를 써줘야 했던 것 같다.

     

     

    • 그럼 언제 레퍼런스를 null로 설정해야 하는가? 메모리를 직접 관리할 때.
    • Stack 구현체처럼 elements라는 배열을 관리하는 경우에 GC는 어떤 객체가 필요 없는 객체인지 알 수 없다.
    • 오직 프로그래머만 elements에서 가용한 부분 (size 보다 작은 부분)과 필요없는필요 없는 부분 (size 보다 큰 부분)을 알 수 있다. 따라서, 프로그래머가 해당 레퍼런스를 null로 만들어서 GC한테 필요 없는 객체들이라고 알려줘야 한다.
    • 메모리를 직접 관리하는 클래스는 프로그래머가 메모리 누수를 조심해야 한다.

     

     

    캐시


     

    • 캐시를 사용할 때도 메모리 누수 문제를 조심해야 한다.
    • 객체의 레퍼런스를 캐시에 넣어 놓고 캐시를 비우는 것을 잊기 쉽다. 여러 가지 해결책이 있지만, 캐시의 키에 대한 레퍼런스가 캐시 밖에서 필요 없어지면 해당 엔트리를 캐시에서 자동으로 비워주는 WeakHashMap을 쓸 수 있다.
    • WeakHashMap을 쓰면 key1에 대한 reference를 weak reference로 감싸서 들어간다.
    • weakReference는 Hard Reference가 쓸모가 없어지면 key1이 Map에 들어가 있음에도 불구하고 정리할 수 있다.
    • 또는 특정 시간이 지나면 캐시값이 의미가 없어지는 경우에 백그라운드 스레드를 사용하거나 (아마도 ScheduledThreadPoolExecutor),
    • 새로운 엔트리를 추가할 때 부가적인 작업으로 기존 캐시를 비우는 일을 할 것이다. (LinkedHashMap 클래스는 removeEldestEntry라는 메서드를 제공한다.)

     

    콜백


    • 세번째로 흔하게 메모리 누수가 발생할 수 있는 지점으로 리스너와 콜백이 있다.
    • 클라이언트 코드가 콜백을 등록할 수 있는 API를 만들고 콜백을 뺄 수 있는 방법을 제공하지 않는다면, 계속해서 콜백이 쌓이기 할 것이다. 이것 역시 WeahHashMap을 사용해서 해결할 수 있다.
    • 메모리 누수는 발견하기 쉽지 않기 때문에 수년간 시스템에 머물러 있을 수도 있다. 코드 인스택션이나 heap profiler 같은 디버깅 툴을 사용해서 찾아야 한다. 따라서 이런 문제를 예방하는 방법을 학습하여 미연에 방지하는 것이 좋다.

     

     

     

    JAVA의 WEAK REFERENCE 란?


    1. Strong reference

    • 보통 만드는 new, 팩토리 메서드등은 모두 Strong 한 reference이다.

    2. Weak Reference

    • Weak Reference는 만든 Strong한 reference를 Weak Reference로 한번 감싸야한다.
    • 가비지 컬렉션의 대상이 되려면 그 객체를 가리키는 레퍼런스가 모두 없어져야 가능하다.
    • Weak Reference에 경우 차이점은 이렇다. Strong 한 reference만 없어지면 Weak Reference도 가비지 컬렉션에 대상이 될 수 있다.

     

    WeakReference weakWidget = new WeakReference(widget);  

     

     

    • 이 widget이 가리키는 레퍼런스가 쓸모 없어지면, widget이 가리키는 Strong reference가 없다면
    • weakWidget이 차지하고 있는 메모리도 GC에 제거 영역의 대상이 된다.

     

    • 다음에 Key1은 Strong 한 reference를 참조하고 있다.
    • key1에 대한 필요가 없어진다 하더라도 cache를 담고 있는 Map 자체가
    • 가리키고 있기에 이 key1이 가리키는 Object는 GC에 대상이 될 수 없다.

     

     

    • 하지만 다음과 같이 2번처럼 WeakHashMap이라면
    • key1 refenrece가 쓸모 없어졌다면 key1이 가리키는 reference가 GC에 대상이 될 수 있다.
    • 왜냐하면 put을 넣어도 내부에서는 WeakReference로 감싸기 때문이다.

     

    참고 자료 


    이펙티브 자바

Designed by Tistory.