垃圾回收機制
當對象被判爲垃圾的標準,即沒有被其他對象引用時,纔會被收回。
判斷對象爲垃圾的方法
引用計數法:
- 通過判斷對象的引用數量來決定對象是否可以被回收
- 每個對象實例都有一個引用計數器,被引用時+1,完成引用時-1
- 任何引用計數爲0的對象都有可以被當做垃圾進行回收
優點:執行效率高,程序執行所受的影響比較小
缺點:如果存在循環引用就會導致出現內存泄漏,就比如:父對象對子對象引用,反過來子對象又對父對象引用,這樣計數器就永遠不可能爲0。
可達性分析算法:
- 通過判斷對象的引用鏈是否可達來決定對象是否可以被回收
算法核心:通過一系列稱爲"GC Roots"的對象作爲起始點,從這些節點開始向下搜索,搜索走過的路徑
稱之爲"引用鏈",當一個對象到GC Roots沒有任何的引用鏈相連時(從GC Roots到這個對象不可達)時,證明此對象
是不可用的。以下圖爲例
可以作爲GC Root的對象:
- 虛擬機棧中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中JNI(即一般說的Native方法)引用的對象
即使在可達性分析算法中不可達的對象,也並非非死不可。只是暫時處於“緩刑”的階段,若要真正死亡還要經歷至少兩次的標記過程:如果對象在進行行可達性分析之後發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法或者finalize()方法已經被JVM調用過,虛擬機會將這兩種情況都視爲"沒有必要執行",此時的對象纔是真正"死"的對象。
如果這個對象被判定爲有必要執行finalize()方法,那麼這個對象將會被放置在一個叫做F-Queue的隊列之中,並在
稍後由一個虛擬機自動建立的、低優先級的Finalizer線程去執行它(這裏所說的執行指的是虛擬機會觸發finalize()
方法)。finalize()方法是對象逃脫死亡的最後一次機會,稍後GC將對F-Queue中的對象進行第二次小規模標記,如
果對象在finalize()中成功拯救自己(只需要重新與引用鏈上的任何一個對象建立起關聯關係即可),那在第二次標記
時它將會被移除出"即將回收"的集合;如果對象這時候還是沒有逃脫,那基本上它就是真的被回收了。注意:任何一個對象的finalize()方法都只會被系統調用一次,如果對象面臨下一次回收,那麼它的finalize()方法不會再被執行。
引用概念擴充:
- 強引用(永久有效):垃圾收集器永遠不會回收的對象
- 軟引用(內存不足):是一些有用但非必須的對象。在系統即將發生內存溢出異常之前,會將這些對象進行回收範圍內的二次回收,如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。
- 弱引用:被弱引用關聯的對象只能生存到下一次垃圾回收之前,當垃圾收集器開始工作時,無論當前內存是否夠用,都將會被回收。
- 虛引用:最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。爲一個對象設置虛引用的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。
可以合理的利用軟引用與弱引用來提升JVM內存的使用性能(作爲緩存使用)
好文傳送門 ---> 通過軟引用和弱引用提升JVM內存使用性能的方法
垃圾回收算法
標記-清除算法
- 標記:標記出所有需要回收的對象
- 清除:在標記完成後統一進行清除
缺點:
效率問題:標記與清除兩個過程的效率都不高
空間問題:標記清除算法會產生大量不連續的內存空間
複製算法(Eden與Survive)新生代回收算法
- 解決碎片化問題
- 順序分配內存,簡單高效
- 適用於對象存活率低的場景,需要被複制的對象不多
缺點:在應對對象存活率較高的場景就不太適合,由於需要大量的複製操作,所以效率很低
標記-整理算法(老年代回收算法)
- 避免內存不連續性
- 不用設置兩塊內存交換
- 適用於存活率較高的場景
分代收集算法(Minor GC和Full GC)
- 按照對象不同生命週期的不同劃分區域而採取不同的垃圾回收算法,進而提高JVM的回收效率
在JDK8之前,java堆內存分爲年輕代,老年代和永久代,JDK8只保留年輕代(複製算法)與老年代(標記-整理算法)
GC的分類
- Minor GC(新生代GC):頻率快
- 年輕代:儘可能快速收集掉那些生命週期短的對象
- Eden區 0.8
- 兩個Survivor區 0.2 1:1
- 默認15次
- 年輕代:儘可能快速收集掉那些生命週期短的對象
- Full GC(老年代GC或Major GC):存放生命週期較長的對象 頻率低
- 標記-清除算法 或 標記-整理算法
觸發Full GC的條件:
- 老年代空間不足
- 永久代空間不足(JDK8之前)
- JDK8之後取消永久代,並將元空間存儲位置改爲本地內存,這樣就會減少Full GC的次數,提升效率
- Minor GC晉升到老年代的平均大小大於老年代的剩餘空間
- 調用System.gc() 對老年代與年輕代均會進行GC,但是該方法只是提醒虛擬機去回收對象,但具體的回收時機還是依靠虛擬機
對象如何晉升到老年代:
- 經歷Minor次數依舊存活的對象
- Survivor區中存放不下的對象
- 新生產的大對象
常用的調優參數:
- -XX:SurvivorRatio:Eden和Survivor的比值,默認爲8:1
- -XX:NewRatio:老年代與年輕代內存大小的比值
- -XX:MaxTenuringThreshold:對象從年輕代晉升到老年代經過GC次數的最大閾值(默認爲15)