Java 对象之死
如何判斷對象“無用”?
關于判斷對象是否無用的算法,在JVM的發展過程中出現過兩種算法:一種是引用計數和根集算法。
引用計數算法
例如下圖中的object1的引用計數是2,GC的時候不回收,object6、object7引用計數為0,GC的時候要被回收。引用計數有個缺點:當引用產生閉環的時候即便是對象實際上已經“無用”也無法回收了,例如下圖中的 ,object4、object5、object8直接引用關系。
引用計數算法
根集算法
引用計數算法簡高效,早期的 Java 虛擬機中使用這個方式,但是正如上面提到的不能解決“引用閉環”的問題,后來的 Java 虛擬機中普普采用根集算法。從 GCRoot(比如一個靜態變量) 開始遍歷引用關系,能遍歷到的,叫做引用可達,遍歷不到的叫做不可達。不可達的對象就被判“死刑了”,GC的時候將被槍斃掉。
根集算法
對象回收之后的內存如何處置?
人死了、遺產處理不好會產生很多糾紛,所以有法律制度。在 JVM 的世界里對象死了,剩下的“遺產”無非就是它占據的那片內存空間。對象死后生下的那部分內存空間進行一下規劃的,具體算法有三種。
三種回收算法.png
標記-清除
標記就是把那些“無用的對象”標記一下,被標記的對象等于被判了死刑,也就是就可以回收了,清除就是變那些被標記了的對象清楚掉。
GC標記之后的狀態
清除之后的狀態
我們發現,清除之后的狀態,其中的可用內存并不是連續的,也就是說內存存在碎片,如果創建一個大對象,無法分配到足夠大的連續內存空間,使得GC不得不做一次重新整理。由于可用對象和無用對象直接的內存不是連續的,所以標記的過程是要遍歷識別內存區域的,清除的過程也是要遍歷識別的,整個過程效率比較低。
標記-復制
標記的過程不變。把內存劃分為兩部分,一部分叫做預留區域(下圖虛線框中),不分配對象。在GC的時候把那些正在使用的對象復制到預留區域,然后再把非預留區域以外的內存全部清除。
標記之后內存狀態
復制之后內存狀態
清除之后內存狀體
解決了效率和內存碎片的問題,但是代價是昂貴的:犧牲了1/2的內存,顯然在很多情況下是無法接受的。
標記-整理
標記的過程依然不變,標記之后處于內存末端區域的正在使用的對象向前移動占據覆蓋那些被標記了的區域(有一種碾壓的感覺),把正在使用的對象趕到一起,再把剩余的標記對象全部清除。
標記之后內存狀體
移動之后的內存狀態
清除之后內存狀體
分代混合算法
在現代虛擬機(通常就是 HotSpot(TM)),使用的分代算法來處理內存,并沒有什么新意,只是針對對象的生命周期范圍來劃分區域,不同的區域使用不同的算法。一般分為新生代和老生代,新生代由于生命不長,GC的時候大部分對象已經死亡,所以有足夠的空間作為擔保,可用使用標記-復制算法,對于老生代老生代使用標記-清除或標記-整理算法。
分代混合算法
Stop the world
抬腳打掃衛生.png
想象一下,你不可能在媽媽一邊打掃衛生的時候你一邊扔垃圾吧,她當然希望你乖乖做在沙發上抬起腳來別動。JVM的世界亦如此,前面我們說道使用引用關系的根集算法來標記對象是否無用,二這個引用關系只是某一時刻的“快照”,使用一個叫做OopMap的數據結構來保存的。引用關系是會隨時間變化的,所以在垃圾回收器進行垃圾回收時候就必須的有所停頓,sun把這個現象叫做“Stop the world ”。
所以頻繁的GC會影響性能,對象存活時間過長會占用內存,在實際開發過程中我們如何去平衡內存空間和執行效率、如何去選擇對象生命周期是非常重要的。
?為了讓學習變得輕松、高效,今天給大家免費分享一套Java教學資源。幫助大家在成為Java架構師的道路上披荊斬棘。需要資料的歡迎加入學習交流群:9285,05736
總結
- 上一篇: Java开发环境搭建详细步骤
- 下一篇: 10大最高效的Java库盘点