在使用 Memory Analyzer tool(MAT)分析內(nèi)存泄漏(一)(以下簡(jiǎn)稱前文)中說(shuō)到:“Soft Ref(軟引用)對(duì)應(yīng)軟可達(dá)性,只要有足夠的內(nèi)存,就一直保持對(duì)象,直到發(fā)現(xiàn)內(nèi)存吃緊且沒(méi)有Strong Ref時(shí)才回收對(duì)象。一般可用來(lái)實(shí)現(xiàn)緩存,通過(guò)java.lang.ref.SoftReference類實(shí)現(xiàn)。”
由于照本宣科,所以我一廂情愿的認(rèn)為只要Strong Ref不可達(dá),那么GC會(huì)自動(dòng)回收Soft Ref可達(dá)的對(duì)象。正好最近項(xiàng)目上遇到一個(gè)舊版本DWR引起的內(nèi)存泄漏(新版已修正),由于不愿更新到DWR的最新版本,所以想用Soft Ref來(lái)實(shí)現(xiàn)。可惜,到最后還是失敗了,原因在于沒(méi)正確使用Soft Ref,那么如何正確使用,在這里聊聊。
由于前文中有提到Weak Ref有個(gè)java.util.WeakHashMap實(shí)現(xiàn)類,所以就從它的源代碼入手吧。WeakHashMap內(nèi)部是一個(gè)Entry[],而Entry是繼承了WeakReference并實(shí)現(xiàn)Map.Entry接口的靜態(tài)類,類聲明:private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V>。好了,由此可知,Entry實(shí)際上是WeakReference的子類,每次實(shí)例化Entry也就是在實(shí)例化WeakReference,在構(gòu)造函數(shù)中調(diào)用super(key, queue)為WeakReference傳遞標(biāo)識(shí)(key)和ReferenceQueue實(shí)例(queue)。ReferenceQueue和WeakReference是聯(lián)合使用的,作用是當(dāng)WeakReference所引用的對(duì)象被回收后,可以通過(guò)WeakReference的poll()來(lái)得到WeakReference,但是請(qǐng)注意,如果再對(duì)得到的WeakReference進(jìn)行g(shù)et(),結(jié)果將是null,因?yàn)楸籛eak Ref的對(duì)象本身已經(jīng)被回收。接著再看WeakHashMap的put(K key, V value)方法,該方法又關(guān)聯(lián)調(diào)用了私有方法expungeStaleEntries(),expungeStaleEntries()的注釋表明,該方法是用來(lái)刪除失效Entry的,這里調(diào)用了ReferenceQueue的poll()方法來(lái)找出被回收的對(duì)象(已被Weak Ref),然后清除,并縮小鍵-值映射關(guān)系的數(shù)目。根據(jù)觀察,例如remove(Object key)、size()、get(Object key)這些經(jīng)常使用的方法,內(nèi)部都優(yōu)先調(diào)用了expungeStaleEntries()。由此可以見(jiàn),在程序運(yùn)行中很可能會(huì)引起被Weak Ref的對(duì)象的回收,所以每次操作都要進(jìn)行WeakReference的poll(),而后續(xù)的清除工作還得手工編碼完成。
好,有了WeakHashMap的實(shí)現(xiàn)經(jīng)驗(yàn),開(kāi)始實(shí)現(xiàn)自己的SoftReference吧。
Pilot類。
SoftRefedPilot類,模擬WeakHashMap的Entry。
測(cè)試類TestSoftReference。
好了,在JVM上加入-XX:+PrintGC參數(shù)觀察GC信息吧。
可以看到,當(dāng)heap達(dá)到64m,隨即被Full GC,正如前文中說(shuō)到的那樣,內(nèi)存吃緊的時(shí)候,Soft Ref開(kāi)始進(jìn)行清理,另外從主觀感受和客觀日志表明,在Full GC的時(shí)候,的確比一般的GC要慢得多,貌似有10倍的差距。所以,利用Soft Ref來(lái)做緩存,這個(gè)效率還得重新考慮。
由于照本宣科,所以我一廂情愿的認(rèn)為只要Strong Ref不可達(dá),那么GC會(huì)自動(dòng)回收Soft Ref可達(dá)的對(duì)象。正好最近項(xiàng)目上遇到一個(gè)舊版本DWR引起的內(nèi)存泄漏(新版已修正),由于不愿更新到DWR的最新版本,所以想用Soft Ref來(lái)實(shí)現(xiàn)。可惜,到最后還是失敗了,原因在于沒(méi)正確使用Soft Ref,那么如何正確使用,在這里聊聊。
由于前文中有提到Weak Ref有個(gè)java.util.WeakHashMap實(shí)現(xiàn)類,所以就從它的源代碼入手吧。WeakHashMap內(nèi)部是一個(gè)Entry[],而Entry是繼承了WeakReference并實(shí)現(xiàn)Map.Entry接口的靜態(tài)類,類聲明:private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V>。好了,由此可知,Entry實(shí)際上是WeakReference的子類,每次實(shí)例化Entry也就是在實(shí)例化WeakReference,在構(gòu)造函數(shù)中調(diào)用super(key, queue)為WeakReference傳遞標(biāo)識(shí)(key)和ReferenceQueue實(shí)例(queue)。ReferenceQueue和WeakReference是聯(lián)合使用的,作用是當(dāng)WeakReference所引用的對(duì)象被回收后,可以通過(guò)WeakReference的poll()來(lái)得到WeakReference,但是請(qǐng)注意,如果再對(duì)得到的WeakReference進(jìn)行g(shù)et(),結(jié)果將是null,因?yàn)楸籛eak Ref的對(duì)象本身已經(jīng)被回收。接著再看WeakHashMap的put(K key, V value)方法,該方法又關(guān)聯(lián)調(diào)用了私有方法expungeStaleEntries(),expungeStaleEntries()的注釋表明,該方法是用來(lái)刪除失效Entry的,這里調(diào)用了ReferenceQueue的poll()方法來(lái)找出被回收的對(duì)象(已被Weak Ref),然后清除,并縮小鍵-值映射關(guān)系的數(shù)目。根據(jù)觀察,例如remove(Object key)、size()、get(Object key)這些經(jīng)常使用的方法,內(nèi)部都優(yōu)先調(diào)用了expungeStaleEntries()。由此可以見(jiàn),在程序運(yùn)行中很可能會(huì)引起被Weak Ref的對(duì)象的回收,所以每次操作都要進(jìn)行WeakReference的poll(),而后續(xù)的清除工作還得手工編碼完成。
好,有了WeakHashMap的實(shí)現(xiàn)經(jīng)驗(yàn),開(kāi)始實(shí)現(xiàn)自己的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;
}
}
測(cè)試類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) {//找出被軟引用回收的對(duì)象
//以key為標(biāo)志,從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) {//找出被軟引用回收的對(duì)象
//以key為標(biāo)志,從map中移除
map.remove(pollref.key);
}
i++;
}
System.out.println("done");
}
}
好了,在JVM上加入-XX:+PrintGC參數(shù)觀察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]
可以看到,當(dāng)heap達(dá)到64m,隨即被Full GC,正如前文中說(shuō)到的那樣,內(nèi)存吃緊的時(shí)候,Soft Ref開(kāi)始進(jìn)行清理,另外從主觀感受和客觀日志表明,在Full GC的時(shí)候,的確比一般的GC要慢得多,貌似有10倍的差距。所以,利用Soft Ref來(lái)做緩存,這個(gè)效率還得重新考慮。
請(qǐng)注意!引用、轉(zhuǎn)貼本文應(yīng)注明原作者:Rosen Jiang 以及出處: http://www.aygfsteel.com/rosen