JVM之垃圾收集策略

背景:看完《深入理解Java虛擬機》和相關博客,對JVM還是沒有一個條理清晰的認識,遂提取了書中相關知識點和參考相關優秀博客並整理成JVM專題博文系列,幫助自己鞏固並理清有關JVM的知識重點,也分享出來給有需要的童鞋,如有差錯,歡迎拍磚!

在之前,我總結了JVM之內存結構中提到各種垃圾回收器,今天就來聊聊它們的回收策略

我們知道JAVA最大的優點就是可以實現自動內存管理,這極大的便利了JAVA程序員,降低了使用成本。但這也使得平時我們在使用JAVA編程時不太關注JVM到底是怎樣進行內存回收的,只有在需要實際對JVM進行系統性能調優,這裏的場景可能是在系統面臨極致性能優化要求時,我們才發現需要對JAVA的整體內存結構以及內存回收機制要有一定的認識和了解才行。

JVM垃圾回收

GC (Garbage Collection)的基本原理:將內存中不再被使用的對象進行回收,GC中用於回收的方法稱爲收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停

對新生代的對象的收集稱爲minor GC;
對舊生代的對象的收集稱爲Full GC;
程序中主動調用System.gc()強制執行的GC爲Full GC。

不同的對象引用類型, GC會採用不同的方法進行回收,JVM對象的引用分爲了四種類型:

強引用:默認情況下,對象採用的均爲強引用(這個對象的實例沒有其他對象引用,GC時纔會被回收)
軟引用:軟引用是Java中提供的一種比較適合於緩存場景的應用(只有在內存不夠用的情況下才會被GC)
弱引用:在GC時一定會被GC回收
虛引用:由於虛引用只是用來得知對象是否被GC

在圖中,我們也大致對整個垃圾回收系統進行了標註,這裏主要涉及回收策略、回收算法、垃圾回收器這幾個部分。形象一點表述,就是JVM需要知道那些內存可以被回收,要有一套識別機制,在知道那些內存可以回收以後具體採用什麼樣的回收方式,這就需要設計一些回收算法,而具體的垃圾回收器就是根據不同內存區域的使用特點,採用相應地回收策略和算法的具體實現了。

我們也標註了不同垃圾回收器所適用的特定內存區域,對於JVM垃圾回收這塊的優化,就是我們需要在瞭解這些垃圾回收算法、垃圾回收器特點後能夠根據自己應用的場景選擇合適的垃圾收集器,以及各區域垃圾收集器的搭配關係。

回收策略

我們知道,JVM進行內存回收的主要目的是爲了回收不再使用的內存,因爲在進行JAVA程序編寫時,我們只有new的操作,而不需要收工釋放不再使用的空間,如果這些空閒內存不能及時被回收,很快我們的JVM內存空間就會泄露(新申請內存空間的操作失敗,導致程序報錯),所以回收不再使用的內存的目的則是爲了及時釋放空間,騰籠換鳥,以防止內存泄漏。

那麼問題來了,JAVA程序申請了那麼多的內存空間,那些內存才能被認定是不再使用的內存呢?搞錯了,如果把正在被程序使用的內存給釋放了,程序邏輯就空指針異常了!

由於程序計數器、Java虛擬機棧、本地方法棧都是線程獨享,其佔用的內存也是隨線程生而生、隨線程結束而回收。而Java堆和方法區則不同,線程共享,是GC的所關注的部分。
在堆中幾乎存在着所有對象,GC之前需要考慮哪些對象還活着不能回收,哪些對象已經死去可以回收,所以回收對象所佔用的內存是JAVA垃圾回收的主要目標。

有兩種算法可以判定對象是否存活:

引用計數算法:給對象中添加一個引用計數器,每當一個地方應用了對象,計數器加1;當引用失效,計數器減1;當計數器爲0表示該對象已死、可回收。但是它很難解決兩個對象之間相互循環引用的情況。

可達性分析算法:通過一系列稱爲“GC Roots”的對象作爲起點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(即對象到GC Roots不可達),則證明此對象已死、可回收。Java中可以作爲GC Roots的對象包括:虛擬機棧中引用的對象、本地方法棧中Native方法引用的對象、方法區靜態屬性引用的對象、方法區常量引用的對象。

那麼如何判斷對象是處於可回收狀態的呢?在主流的JVM中是採用“可達性分析算法”來進行判斷的。

這個算法的基本思路就是通過一系列的稱爲“GC Roots”的對象作爲起始點,並從這些節點開始往下進行搜索,搜索走過的路徑我們稱之爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,我們就稱之爲對象引用不可達,則證明這個對象是不可用的,就可以暫時判定這個對象爲可回收對象。示意圖如下:

在圖中雖然Obj F與Obj J之間互相有關聯但是它們到GC Roots是不可達的,所以將會被判定爲可回收對象。既然如此,什麼樣的對象可以作爲GC Roots對象呢?
在JAVA中可以被作爲GC Roots的對象主要是:虛擬機棧-棧幀中的本地變量表所引用的對象、方法區(<JDK1.8)中類靜態屬性所引用的對象/常量屬性所引用的對象、本地方法棧中引用的對象。

這裏還需要注意一個小的細節,就是被判定爲對象不可達的對象也並非會被立刻回收,在學習JAVA語法是我們應該學習過finalize()方法,如果對象重寫了finalize方法,並重新把this關鍵字賦值給了某個類變量或對象的成員變量的話,該對象就會被"救活",具體過程可參考上圖所示,只是這種方式並不鼓勵大家使用,瞭解下就行。

在關於如何判定對象是否屬於不再使用的內存時,還有個通常會被大家錯誤認爲是JVM使用的方式-“引用計數法”,事實上引用計數法的實現比較簡單,判定效率也比較高,在Python語言中就使用了這種算法進行內存管理,但是它有一個比較難解決的對象之間循環引用的問題,所以在JAVA虛擬機裏並沒有選用“引用計數法”來管理內存,需要大家注意下!

參考

面試必問之JVM原理
一張圖看懂JVM之垃圾回收算法詳解


技術討論 & 疑問建議 & 個人博客

版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 許可協議,轉載請註明出處!

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