聊聊Java中引用類型-引用類型應(yīng)用與內(nèi)存泄漏
本文將接著上一篇文章內(nèi)容,聊聊Java中引用使用以及可能產(chǎn)生的內(nèi)存泄漏。
Java程序員是幸福的,不用過多考慮內(nèi)存申請和釋放,Jvm在Java與C++之間構(gòu)建一堵由內(nèi)存動態(tài)分配和垃圾收集技術(shù)所圍成的高墻,是的Java程序員能全身心投入到實際開發(fā)當(dāng)中,是否會有墻外面人想進去,墻里面的人卻想出來呢?
內(nèi)存溢出和內(nèi)存泄漏:
內(nèi)存溢出:俗稱OOM,指JVM無法申請到足夠內(nèi)存空間或者GC失敗,而拋出的Error,OOM造成的后果十分嚴重,使得應(yīng)用無法對外提供服務(wù)。
內(nèi)存泄漏:部分內(nèi)存已經(jīng)沒有用了,但是卻沒有被回收,對于Java而言,就是GC無法回收這部分本應(yīng)該被回收的內(nèi)存。
Obeject 的 finalize
Object 的finalize方法,當(dāng)對象即將被回收時,可以被執(zhí)行finalize方法,但是并不能依賴這個方法來清除,否則將造成內(nèi)存泄漏。例如當(dāng)進行socket編程時,需要在finally塊中執(zhí)行close方法,從而釋放資源,如果忘記釋放了,則可能會造成內(nèi)存泄漏,例如在 重寫了finalize方法:
/*** Cleans up if the user forgets to close it.*/protectedvoid finalize()throwsIOException{close();}
方法注釋也很簡潔明了,所以在釋放這一類工具類時,一定要手動執(zhí)行close方法,而不是交給finalize去替我們釋放,這樣容易引發(fā)內(nèi)存泄漏。
ThreadLocal
ThreadLocal很常見,原理不難理解,在Thread類中有兩個ThreadLocalMap變量,維護著多個本地線程變量:Thread:
/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals =null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals =null;
ThreadLocalMap結(jié)構(gòu):
privatestaticfinalint INITIAL_CAPACITY =16;privateEntry[] table;// 存放元素的數(shù)組privateint size =0;// 大小privateint threshold;// Default to 0
其Entry為一個WeakReference子類,reference為對應(yīng)的ThreadLocal 實例,即如果沒有其他引用,就會被回收:
staticclassEntryextendsWeakReference<ThreadLocal>>{/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal> k,Object v){super(k);value = v;}}
Entry 由于沒有使用引用隊列,故一旦沒有引用,則會直接變?yōu)閕nactive狀態(tài),從而被gc回收。但是另一方面,ThreadLocalMap 中 Entry[] 為一個引用,所以事實上,就算ThreadLocal沒有其他地方使用,也會被Thread引用,所以只要Thread不銷毀,Entry 并不會因WeakReference特性而銷毀。另一方面,由于Entry 中key(ThreadLocal)是弱引用類型,所以一旦ThreadLocal沒有被引用,那么ThreadLocal將會在下次gc被回收,造成的效果為:
Entry 不為null。
Entry實例的get方法獲取為null,因為ThreadLocal已經(jīng)被回收了,但是value仍然存在,這就造成了泄漏。
在ThreadLocalMap的set操作過程,雖然在檢查到上述第二種情況,并且會嘗試將數(shù)組內(nèi)所有滿足該情況的元素節(jié)點的value都設(shè)置為null。這也是為啥追求極致性能的netty會自己造一個FastThreadLocal來取代TheadLocal:https://blog.csdn.net/anLA_/article/details/110777608所以在使用ThreadLocal時,在用完時,一定要執(zhí)行remove方法,清除對應(yīng)引用。
Netty中內(nèi)存泄漏檢測
netty中通過BufAllocator使用ByteBuf時,都會包裝一層校驗內(nèi)存泄漏的邏輯:
protectedstaticByteBuf toLeakAwareBuffer(ByteBuf buf){ResourceLeakTracker<ByteBuf> leak;switch(ResourceLeakDetector.getLevel()){case SIMPLE:leak =AbstractByteBuf.leakDetector.track(buf);if(leak !=null){buf =newSimpleLeakAwareByteBuf(buf, leak);}break;case ADVANCED:case PARANOID:leak =AbstractByteBuf.leakDetector.track(buf);if(leak !=null){buf =newAdvancedLeakAwareByteBuf(buf, leak);}break;default:break;}return buf;}
上述會將ByteBuf封裝一層 DefaultResourceLeak,而 DefaultResourceLeak 則是一個WeakReference對象:
DefaultResourceLeak(Object referent,ReferenceQueue<Object> refQueue,Set<DefaultResourceLeak>> allLeaks){super(referent, refQueue);assert referent !=null;// Store the hash of the tracked object to later assert it in the close(...) method.// It's important that we not store a reference to the referent as this would disallow it from// be collected via the WeakReference.trackedHash =System.identityHashCode(referent);allLeaks.add(this);// Create a new Record so we always have the creation stacktrace included.headUpdater.set(this,newRecord(Record.BOTTOM));this.allLeaks = allLeaks;}
在應(yīng)用中,對返回的ByteBuf對象進行操作時,都會間接調(diào)用 ResourceLeakDetector 的 reportLeak 方法:
privatevoid reportLeak(){if(!logger.isErrorEnabled()){clearRefQueue();return;}// Detect and report previous leaks.for(;;){@SuppressWarnings("unchecked")DefaultResourceLeakref=(DefaultResourceLeak) refQueue.poll();// 如果有弱引用被回收,則會進入隊列if(ref==null){break;}if(!ref.dispose()){// 如果已經(jīng)釋放,則不是泄漏continue;}String records =ref.toString();if(reportedLeaks.putIfAbsent(records,Boolean.TRUE)==null){if(records.isEmpty()){reportUntracedLeak(resourceType);}else{reportTracedLeak(resourceType, records);}}}}
上述方法有以下要點:
如果進入refQueue,則說明有弱引用被回收,如果dispose了,則說明回收之前執(zhí)行release,釋放了資源,否則沒有釋放。
報告泄漏最近調(diào)用路徑
使用MAT進行堆dump分析
Java引用類型,還有一個用途點,就是在使用eclipse mat工具時,由于Java對象通過引用鏈接,所以當(dāng)找到一個大對象時,可以過濾其他引用類型,直接選擇強引用,從而一步一步最終拿到當(dāng)時調(diào)用鏈路棧:
這樣就能輕而易舉解決多數(shù)OOM問題的根源。
總結(jié)
Java引用類型很強大,可以來實現(xiàn)一些高校的應(yīng)用內(nèi)緩存,可以監(jiān)聽gc動作等。
根據(jù)部分類文檔及結(jié)合代碼來確定 使用時要注意是否需要手動釋放。
覺得對你有幫助?不如關(guān)注博主公眾號: 六點A君
