在使用 Memory Analyzer tool(MAT)分析內存泄漏(一)(以下簡稱前文)中說到:“Soft Ref(軟引用)對應軟可達性,只要有足夠的內存,就一直保持對象,直到發現內存吃緊且沒有Strong Ref時才回收對象。一般可用來實現緩存,通過java.lang.ref.SoftReference類實現。”
由于照本宣科,所以我一廂情愿的認為只要Strong Ref不可達,那么GC會自動回收Soft Ref可達的對象。正好最近項目上遇到一個舊版本DWR引起的內存泄漏(新版已修正),由于不愿更新到DWR的最新版本,所以想用Soft Ref來實現??上?,到最后還是失敗了,原因在于沒正確使用Soft Ref,那么如何正確使用,在這里聊聊。
由于前文中有提到Weak Ref有個java.util.WeakHashMap實現類,所以就從它的源代碼入手吧。WeakHashMap內部是一個Entry[],而Entry是繼承了WeakReference并實現Map.Entry接口的靜態類,類聲明:private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V>。好了,由此可知,Entry實際上是WeakReference的子類,每次實例化Entry也就是在實例化WeakReference,在構造函數中調用super(key, queue)為WeakReference傳遞標識(key)和ReferenceQueue實例(queue)。ReferenceQueue和WeakReference是聯合使用的,作用是當WeakReference所引用的對象被回收后,可以通過WeakReference的poll()來得到WeakReference,但是請注意,如果再對得到的WeakReference進行get(),結果將是null,因為被Weak Ref的對象本身已經被回收。接著再看WeakHashMap的put(K key, V value)方法,該方法又關聯調用了私有方法expungeStaleEntries(),expungeStaleEntries()的注釋表明,該方法是用來刪除失效Entry的,這里調用了ReferenceQueue的poll()方法來找出被回收的對象(已被Weak Ref),然后清除,并縮小鍵-值映射關系的數目。根據觀察,例如remove(Object key)、size()、get(Object key)這些經常使用的方法,內部都優先調用了expungeStaleEntries()。由此可以見,在程序運行中很可能會引起被Weak Ref的對象的回收,所以每次操作都要進行WeakReference的poll(),而后續的清除工作還得手工編碼完成。
好,有了WeakHashMap的實現經驗,開始實現自己的SoftReference吧。
Pilot類。
SoftRefedPilot類,模擬WeakHashMap的Entry。
測試類TestSoftReference。
好了,在JVM上加入-XX:+PrintGC參數觀察GC信息吧。
可以看到,當heap達到64m,隨即被Full GC,正如前文中說到的那樣,內存吃緊的時候,Soft Ref開始進行清理,另外從主觀感受和客觀日志表明,在Full GC的時候,的確比一般的GC要慢得多,貌似有10倍的差距。所以,利用Soft Ref來做緩存,這個效率還得重新考慮。
由于照本宣科,所以我一廂情愿的認為只要Strong Ref不可達,那么GC會自動回收Soft Ref可達的對象。正好最近項目上遇到一個舊版本DWR引起的內存泄漏(新版已修正),由于不愿更新到DWR的最新版本,所以想用Soft Ref來實現??上?,到最后還是失敗了,原因在于沒正確使用Soft Ref,那么如何正確使用,在這里聊聊。
由于前文中有提到Weak Ref有個java.util.WeakHashMap實現類,所以就從它的源代碼入手吧。WeakHashMap內部是一個Entry[],而Entry是繼承了WeakReference并實現Map.Entry接口的靜態類,類聲明:private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V>。好了,由此可知,Entry實際上是WeakReference的子類,每次實例化Entry也就是在實例化WeakReference,在構造函數中調用super(key, queue)為WeakReference傳遞標識(key)和ReferenceQueue實例(queue)。ReferenceQueue和WeakReference是聯合使用的,作用是當WeakReference所引用的對象被回收后,可以通過WeakReference的poll()來得到WeakReference,但是請注意,如果再對得到的WeakReference進行get(),結果將是null,因為被Weak Ref的對象本身已經被回收。接著再看WeakHashMap的put(K key, V value)方法,該方法又關聯調用了私有方法expungeStaleEntries(),expungeStaleEntries()的注釋表明,該方法是用來刪除失效Entry的,這里調用了ReferenceQueue的poll()方法來找出被回收的對象(已被Weak Ref),然后清除,并縮小鍵-值映射關系的數目。根據觀察,例如remove(Object key)、size()、get(Object key)這些經常使用的方法,內部都優先調用了expungeStaleEntries()。由此可以見,在程序運行中很可能會引起被Weak Ref的對象的回收,所以每次操作都要進行WeakReference的poll(),而后續的清除工作還得手工編碼完成。
好,有了WeakHashMap的實現經驗,開始實現自己的SoftReference吧。
Pilot類。
/**
* Pilot class
* @author rosen jiang
*/
package org.rosenjiang.bo;
public class Pilot{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
* Pilot class
* @author rosen jiang
*/
package org.rosenjiang.bo;
public class Pilot{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
SoftRefedPilot類,模擬WeakHashMap的Entry。
/**
* SoftRefedPilot class
* @author rosen jiang
*/
package org.rosenjiang.bo;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public class SoftRefedPilot extends SoftReference<Pilot> {
public int key;
public SoftRefedPilot(int key, Pilot referent, ReferenceQueue<Pilot> q) {
super(referent, q);
this.key = key;
}
}
* SoftRefedPilot class
* @author rosen jiang
*/
package org.rosenjiang.bo;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public class SoftRefedPilot extends SoftReference<Pilot> {
public int key;
public SoftRefedPilot(int key, Pilot referent, ReferenceQueue<Pilot> q) {
super(referent, q);
this.key = key;
}
}
測試類TestSoftReference。
/**
* TestSoftReference class
* @author rosen jiang
*/
package org.rosenjiang.test;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import java.util.Map;
import org.rosenjiang.bo.Pilot;
import org.rosenjiang.bo.SoftRefedPilot;
public class TestSoftReference {
public static void main(String[] args) {
soft();
}
static void soft(){
Map<Integer, SoftRefedPilot> map = new HashMap<Integer, SoftRefedPilot>();
ReferenceQueue<Pilot> queue = new ReferenceQueue<Pilot>();
int i = 0;
while (i < 10000000) {
Pilot p = new Pilot();
map.put(i, new SoftRefedPilot(i, p, queue));
//p = null;
SoftRefedPilot pollref = (SoftRefedPilot) queue.poll();
if (pollref != null) {//找出被軟引用回收的對象
//以key為標志,從map中移除
map.remove(pollref.key);
}
i++;
}
System.out.println("done");
}
}
* TestSoftReference class
* @author rosen jiang
*/
package org.rosenjiang.test;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import java.util.Map;
import org.rosenjiang.bo.Pilot;
import org.rosenjiang.bo.SoftRefedPilot;
public class TestSoftReference {
public static void main(String[] args) {
soft();
}
static void soft(){
Map<Integer, SoftRefedPilot> map = new HashMap<Integer, SoftRefedPilot>();
ReferenceQueue<Pilot> queue = new ReferenceQueue<Pilot>();
int i = 0;
while (i < 10000000) {
Pilot p = new Pilot();
map.put(i, new SoftRefedPilot(i, p, queue));
//p = null;
SoftRefedPilot pollref = (SoftRefedPilot) queue.poll();
if (pollref != null) {//找出被軟引用回收的對象
//以key為標志,從map中移除
map.remove(pollref.key);
}
i++;
}
System.out.println("done");
}
}
好了,在JVM上加入-XX:+PrintGC參數觀察GC信息吧。
[GC 55120K->54791K(65088K), 0.0307371 secs]
[GC 58887K->58558K(65088K), 0.0313663 secs]
[Full GC 62654K->52534K(65088K), 0.3171671 secs]
[GC 56630K->56301K(65088K), 0.0278301 secs]
[GC 60397K->60068K(65088K), 0.0303315 secs]
[Full GC 64164K->55894K(65088K), 0.3330122 secs]
[GC 59990K->59660K(65088K), 0.0273494 secs]
[Full GC 63756K->63179K(65088K), 0.3415388 secs]
[Full GC 64640K->43968K(65088K), 0.3204639 secs]
[GC 48064K->47735K(65088K), 0.0329379 secs]
[GC 58887K->58558K(65088K), 0.0313663 secs]
[Full GC 62654K->52534K(65088K), 0.3171671 secs]
[GC 56630K->56301K(65088K), 0.0278301 secs]
[GC 60397K->60068K(65088K), 0.0303315 secs]
[Full GC 64164K->55894K(65088K), 0.3330122 secs]
[GC 59990K->59660K(65088K), 0.0273494 secs]
[Full GC 63756K->63179K(65088K), 0.3415388 secs]
[Full GC 64640K->43968K(65088K), 0.3204639 secs]
[GC 48064K->47735K(65088K), 0.0329379 secs]
可以看到,當heap達到64m,隨即被Full GC,正如前文中說到的那樣,內存吃緊的時候,Soft Ref開始進行清理,另外從主觀感受和客觀日志表明,在Full GC的時候,的確比一般的GC要慢得多,貌似有10倍的差距。所以,利用Soft Ref來做緩存,這個效率還得重新考慮。
請注意!引用、轉貼本文應注明原作者:Rosen Jiang 以及出處: http://www.aygfsteel.com/rosen