JVM——垃圾回收機制

重點

內存分配策略、垃圾收集器(G1)、GC算法、GC參數、對象存活的判定
垃圾回收需要思考的3件事情:

  • 哪些內存需要回收
  • 什麼時候回收
  • 如何回收

對象存活的判定

垃圾回收之前,要判斷哪些對象需要回收。

引用計數算法

給對象添加一個引用計數器,當有引用引用這個對象時,計數器加一,當引用失效時,計數器減一,當計數器爲0時,這個對象就是沒有被引用的。
這種方式實現簡單,判斷效率也高,但是這種方式沒有辦法解決互相循環引用的問題,所以Java虛擬機沒有使用這個方式。互相循環引用,比如有兩個對象objA和objB,都有一個實例instance,並且objA.instance=objB,objB.instance = objA。實際上這兩個對象都不可能再訪問,但是因爲互相引用,導致計數器的值不爲0,所以GC收集器沒有辦法回收。

可達性分析算法

Java採用可達性分析算法來判定這個對象是否存活。可達性分析算法的思想:
以“GC root”對象作爲起點,從這個節點向下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到這個“GC root”沒有任何引用鏈可以到達時,證明這個對象是不可用的,判定這個對象爲可回收對象。
在Java語言中,可以作爲GC root 的節點的包括以下幾種:

  • 虛擬機棧中的引用對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中native方法引用的對象

引用

在JDK1.2之前對引用的描述:如果reference類型數據存儲的是另一塊內存地址的起始地址,就稱這塊內存代表着一個引用。
但是這種描述過於狹隘,希望描述一個對象:當內存空間還足夠時,這個對象能保留在內存中。如果內存空間在進行垃圾回收之後還是很緊張,則可以拋棄這類對象。
在JDK1.2之後,Java引用的概念得到了擴充,將引用分爲:強引用、軟引用、弱引用和虛引用。4中引用的強度依次減弱。
強引用:強引用在代碼中普遍存在的,類似“Object obj = new Object()”這種,只要強引用還在,垃圾回收就不會回收被引用的對象。
軟引用:用來描述還有用當非必須的對象。對於軟引用關聯的對象,在發生內存溢出之前,會把這些對象列入回收範圍進行第二次回收。如果這次回收內存空間依然不足,纔會發生內存溢出。使用SoftReference類實現軟引用。
弱引用:也是用來描述非必須對象,但是強度弱於軟引用。當垃圾收集器工作時,無論內存空間是否充足,都會將回收掉被弱引用關聯的對象。使用WeakReference類實現弱引用。
虛引用:也稱爲幽靈引用或者幻影引用。一個對象是否有虛引用的存在,不會對其生存時間造成影響,也無法虛引用取的一個對象的實例,其唯一的目的就是在這個對象被垃圾回收時收到一個通知。使用P’hantomReference類實現。

生存還是死亡

在可達性分析算法中,不可達的對象,也並非一定會被回收,一個對象真正被回收,至少要經歷2次唄標記的過程,如果可達性分析之後,這個對象沒有個GC root關聯上,那麼他將會被第一次標記,並且進行一次篩選,篩選條件是這個對象是否有必要執行finalize方法。如果沒有覆蓋finalize方法或者finalize方法已經被虛擬機執行調用過了,則被視爲沒必要執行。如果是有必要執行finalize方法,那麼這個對象會被放置在一個F-Queue隊列中,然後由一個低優先級的Finalizer線程去執行。如果對象在此期間能夠重新和引用鏈上的任何對象關聯上,那麼這個對象就不會唄回收。否則,進行第二次標記時,這個對象就會被真正的回收了。

回收方法區

在方法區中進行垃圾回收的性價比比較低,因爲在堆中,進行一次垃圾回收,可以回收70%~95%的空間。方法區垃圾回收主要分爲兩部分,廢棄的常量和無用的類。回收廢棄的常量和回收對象類似,沒有引用和這個常量進行關聯,那麼可能會被回收。回收廢棄的類需要滿足下列條件:

  • 該類的所有實例都已經被回收。
  • 加載該類的ClassLoader已經被回收。
  • 該類對應的Class對象,沒有在人任何地方被引用,無法在任何地方可以通過反射訪問該類的方法。
    滿足上述條件,可以對類回收,但是不是一定要回收。可以通過參數設置。

幾種垃圾收集算法的思想

標記——清除算法

標記清除算法是最基礎的垃圾收集算法,分爲“標記”和“清除”兩個階段,首先標記出所有的要被回收的對象,標記完成之後統一回收被標記的對象。這種算法主要不足有兩個:標記和清除兩個過程的效率都不高。另外一個是空間問題,標記清除之後會造成大量不連續的空間碎片,在程序需要分配比較大的對象的空間時,沒有足夠的內存不得不觸發下一次垃圾回收。

複製算法

複製算法將內存分爲大小相等的兩塊,每次只使用一塊,當一塊用完了,將存活的對象複製到另一塊,然後將使用那一塊全部清除掉。這種算法不用考慮內存碎片的情況,實現簡單,高效,但是代價是將內存縮小了一半。
在實際的商業虛擬中,使用複製算法來回收新生代,將內存分爲一個較大的Eden空間和2塊比較小的survivor空間,每次使用一塊Eden和一塊survivor空間,每次回收時,將Eden和survivor中存活的對象一次性複製到另一塊survivor中,然後清零掉剛剛使用的Eden和survivor空間。在Hotspot中,Eden和survivor比例是8:1,所以有10%的空間會被“浪費”,當survivor不夠時,需要依賴其他內存進行擔保。

標記整理算法

老年代的特點是對象存活率比較高,所以出現了標記整理算法,標記需要被回收的對象,然後對存活的對象向另一端移動,然後直接將邊界外的對象清除。

分代收集算法

當前商業虛擬機採用的這種算法,一般將Java堆分成2代,新生代和老年代,新生代每次垃圾收集都會有大量的對象被回收,少量存活,所以使用複製算法,而老年代對象存活率比較高,使用標記清除或者標記整理算法。

垃圾收集器和內存分配策略……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章