해당 포스트의 작성목적
앞서 배운 GC, JVM포스팅으로 Static변수에 참조자료형의 객체가 할당되면 자바프로그램이 종료될때까지 살아있기 때문에, GC가 메모리 회수를 할 수가 없으므로, 메모리릭이 발생할 수있음을 알게되었다. 그렇다면 이렇게 Static변수에 할당된 메모리들을 다시 해제하려면 어떻게 해야할까?? 그 방법을 알아보자
NULL 로 해제하자
해당 객체의 참조를 받고 있는 변수에 null을 할당한다. 객체의 참조가 사라지면서, 힙메모리에 언리쳐블 상태가 된 객체를 GC가 메모리를 수거 할 것이다.
하지만 좋은 방법이 아니다. 소스코드 또한 더러워지며.. 반드시 필요할때만 사용하도록 하자.
라고한다... 뭔가 정확히 왜 안좋은지는 모르겠다. 그냥 안좋을것 같은느낌.. NullException이 나올수 있을것 같은데, 오히려 좋다고 한다. 참조하지 않는 객체를 참조하게 되면, 컴파일러상 오류가 나오지 않아 나중에 문제가 될때 디버깅하기 힘들다고...한다... 이게 unchecked exception인것 같다!... 아직 제대로 안봤지만!..
java.lang.ref
최초의 Java에서는 GC 작업에 애플리케이션의 사용자 코드가 관여하지 않도록 구현되어 있었습니다. 그러나 좀 더 다양한 방법으로 객체를 처리하려는 요구가 있었습니다. 이에 따라 JDK 1.2부터는
java.lang.ref 패키지를 추가해 제한적이나마 사용자 코드와 GC가 상호작용할 수 있게 하고 있습니다.
(GC작업에 개발자가 손을 쓸수없어 Java는 Managed Language이다. 개발자가 직접 메모리관련 작업에 손을 대는 경우는 UmanagedLanguage이며, c, cpp등이 속한다.)
java.lang.ref 패키지는 전형적인 객체 참조인 strong reference 외에도
soft, weak, phantom 3가지의 새로운 참조 방식을 각각의 Reference 클래스로 제공한다. 이중 phantom은 잘 쓰이지 않는다고 하니, soft와 weak에 대해서 중점적으로 알아보자.
Reference와 Reachability
Java GC는 root set으로부터 시작해서 객체에 대한 모든 경로를 탐색하고 그 경로에 있는 reference object들을 조사하여 그 객체에 대한 reachability를 결정한다. 다양한 참조 관계의 결과, 하나의 객체는 다음 5가지 reachability 중 하나가 될 수 있다.
- strongly reachable: root set으로부터 시작해서 어떤 reference object도 중간에 끼지 않은 상태로 참조 가능한 객체, 다시 말해, 객체까지 도달하는 여러 참조 사슬 중 reference object가 없는 사슬이 하나라도 있는 객체
- softly reachable: strongly reachable 객체가 아닌 객체 중에서 weak reference, phantom reference 없이 soft reference만 통과하는 참조 사슬이 하나라도 있는 객체
- weakly reachable: strongly reachable 객체도 softly reachable 객체도 아닌 객체 중에서, phantom reference 없이 weak reference만 통과하는 참조 사슬이 하나라도 있는 객체
- phantomly reachable: strongly reachable 객체, softly reachable 객체, weakly reachable 객체 모두 해당되지 않는 객체. 이 객체는 파이널라이즈(finalize)되었지만 아직 메모리가 회수되지 않은 상태이다.
- unreachable: root set으로부터 시작되는 참조 사슬로 참조되지 않는 객체
Strong-Reference
잠깐 설명하자면, Strong-Reference는 java.lang.ref클래스를 사용하지않는, 일반적인 참조의 형태를 말한다. (int[] arr = new int[6];)
Weak-Reference
java.lang.ref.WeakReference 클래스는
참조 대상인 객체를 캡슐화(encapsulate)한 WeakReference 객체를 생성한다. 이렇게 생성된 WeakReference 객체는 다른 객체와 달리
Java GC가 특별하게 취급한다. 캡슐화된 내부 객체는 weak reference에 의해 참조된다.
다음은 WeakReference 클래스가 객체를 생성하는 예이다.
WeakReference<Sample> wr = new WeakReference<Sample>( new Sample());
Sample ex = wr.get();
...
ex = null;
wr은 new연산자로 생성된 Sample객체를 캡슐화한 객체로 내부적으로 Sample객체를 참조하고 있다. 또 두번째 줄의 get()메소드로, ex와 wr모두 하나의 Sample 객체를 가르킨다.
위 코드의 마지막 줄에서 ex 참조에 null을 대입하면 처음 생성한 Sample 객체는 오직 WeakReference 내부에서만 참조된다. 이 상태의 객체를 weakly reachable 객체라고한다.
Java에서는 SoftReference, WeakReference, PhantomReference 3가지 클래스에 의해 생성된 객체를 "reference object"라고 부른다. 또한 이들 reference object에 의해 참조된 객체는 "referent"라고 부른다. 위의 소스 코드에서 new WeakReference() 생성자로 생성된 객체(wr)는 reference object이고, new Sample() 생성자로 생성된 객체는 referent이다.
다음 그림은 WeakReference로 생성시 메모리의 그림이다.
녹색으로 표시한 중간의 두 객체는 WeakReference로만 참조된 weakly reachable 객체이고, 파란색 객체는 strongly reachable 객체이다. GC가 동작할 때, unreachable 객체뿐만 아니라
weakly reachable 객체도 가비지 객체로 간주되어 메모리에서 회수된다. root set으로부터 시작된 참조 사슬에 포함되어 있음에도 불구하고 GC가 동작할 때 회수되므로,
참조는 가능하지만 반드시 항상 유효할 필요는 없는 LRU 캐시와 같은 임시 객체들을 저장하는 구조를 쉽게 만들 수 있다.위 그림에서
WeakReference 객체 자체는 weakly reachable 객체가 아니라
strongly reachable 객체이다. 또한, 그림에서 A로 표시한 객체와 같이
WeakReference에 의해 참조되고 있으면서 동시에 root set에서 시작한 참조 사슬에 포함되어 있는 경우에는 weakly reachable 객체가 아니라 strongly reachable 객체이다.
GC가 동작하여 어떤 객체를 weakly reachable 객체로 판명하면, GC는 WeakReference 객체에 있는
weakly reachable 객체에 대한 참조를 null로 설정한다. 이에 따라 weakly reachable 객체는 unreachable 객체와 마찬가지 상태가 되고, 가비지로 판명된 다른 객체들과 함께 메모리 회수 대상이 된다.
(결국 참조에의한 메모리할당 회수는 null할당인데, 그냥 코딩으로 null을 할당하느것과 무슨 차이가 있을까?....)
Soft-Reference
softly reachable객체, 즉 Strong reachable 객체가 아니면서, 오직 SoftReference 객체로만 참조된 객체는 힙에 남아있는 메모리의 크기와 객체 사용 빈도에 따라 GC여부가 결정된다, weakly reachble객체와는 달리 GC가 동작할때마다 회수되지 않고, 자주사용할 수록 더 오래 살아남는다. 이에 따라 Oracle Hotspot VM에서는 soft reachable 객체의 GC를 조절하기 위한 다음과 같은 옵션도 제공한다.
-XX:SoftRefLRUPolicyMSPerMB=<N>
soft reachable 객체의 GC여부는 위에서 설정한 N값과 다음 수식에 의해 결정된다
마지막 strong reference가 GC된 때로부터 지금까지의 시간) > (옵션 설정값 N) * (힙에 남아있는 메모리 크기)
만약 힙에 남아있는 메모리가 100mb라면, 1000ms/mb * 100mb = 100s 즉,100초간 객체가 사용되지 않으면 GC의 대상이 된다. GC의 대상이되면 weakly reachable 객체와 마찬가지로, null을 할당 함으로써 GC 작업을 진행한다.
Phantom-Reference
ReferenceQueue
Phantomly reachable 객체의 동작과 Reference를 설명하기 전에, ReferenceQueue에 대해서 먼저 알아볼 필요가 있다.
Soft/Weak Reference 객체는 참조중이던 referent 객체가 사라지면 GC에 의해 자동으로 ReferenceQueue에 enqueue된다. poll()메소드나 remove()메소드로 ReferenceQueue에 Soft/Weak Reference 객체가 들어있는지 확인 할 수 있으며, 이에 관련된 리소스나 객체에 대한 후처리 작업을 진행 할 수 있다. Soft/Weak Reference는 이 ReferenceQueue를 사용 할 수도 있고, 안할 수도있지만, Phantom Reference는 무조건 사용해야한다.
ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
PhantomReference<Object> pr = new PhantomReference<Object>(referent, rq);
PhantomReachable 과 PhantomReference
java에서 Resource를 정리하는 메소드
GC대상 여부를 결정하는 soft/weak reference와는 달리, phantom reference는 fianallize 작업이 이루어진 후에 GC알고리즘에 따라 메모리를 회수 한다. PhantomReference로만 참조되는 객체는 먼저 finallize된 이후에서야 Phantomly Reachable객체로 간주된다. 즉 객체에 대한 참조가 PhantomReference만 남게되면 객체는 finallize 된다.
다음은 GC가 객체를 처리하는 순서이다.
- soft reference
- weak reference
- finalize
- phantom references
- 메모리 회수
GC는 객체의 rechability를 strongly, softly, weakly로 판별하고, 모두 아니라면 finalize를 진행한다. 이후에 그 객체를 참조하는 PhantomReference가 있다면 그 객체를 phantom reachable 객체로 간주하여 PhantomReference를 ReferenceQueue에 넣고 finallize 이후 작업을 애플리케이션이 수행하게 하고 메모리 회수는 지연시킨다. 또한 phantomly reachable로 판명난 객체는 더이상 사용할 수없으며, GC가 자동으로 null설정을 하지 않아 후처리 이후 사용자 코드에서 명시적으로 clear()메소드를 실행해 null로 설정해야 메모리가 회수된다.
포스트를 작성하며 알게된 점들
처음엔 Static변수에 할당된 참조자료형들의 객체는 사라지지 않으니, 주의해서 Static을 사용해야 한다고만 알고있었다. 그러나 직접 null을 할당한다던지, WeakReference나 SoftReference를 사용하면 GC작업에 제한적이나마 개입 할 수 있다는 것을 알게되었다. 다만 궁금한 것은 Weak/Soft 모두 결국에는 null 값을 할당해 유효한 참조를 끊는것에 있다. 단순히 static 변수에 할당된 힙메모리의 객체의 참조를 끊어내는 용도로는 굳이 java.lang.ref클래스 보다는 null값을 할당하는게 더 직관적이고 간편해 보인다. 다만 ref클래스들은 ReferenceQueue의 제공으로 메모리 회수후 후처리 작업을 할 수 있다는 점?... 뭐랄까 다른 방법들이 있지 않을까 싶은데, 아직은 직접 null값을 할당하는 법과, ref클래스를 이용하는 법 두가지만 조사되었다. 예전에 GC를 공부하면서 한번 봤던듯한 내용인데... 이상하게 다시 찾으려니 안보인다.
References
[Effective Java] item7. 다 쓴 객체 참조를 해제하라
자바의 메모리 관리 -Weak,Strong,Phantom reference
자바 WeakReference와 SoftReference의 차이점
자바 참조유형(Strong/Soft/Weak/Phantom)
자바의 강한참조와 약한참조
'프로그래밍 > Java' 카테고리의 다른 글
JAVA의 역사와 JVM-1 (0) | 2021.05.18 |
---|---|
Annotation (0) | 2021.05.17 |
클래스안의 클래스-2 (0) | 2021.05.17 |
GC Log보는 방법 (0) | 2021.05.13 |
클래스 안의 클래스-1 (0) | 2021.05.13 |
Uploaded by Notion2Tistory v1.1.0