您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關JVM中GC垃圾回收原理是什么,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
GC 垃圾回收原理
1 如何判斷對象是垃圾 ?
有兩種經典的判斷方法,借用網友的圖(文中最后有給出鏈接):
引用計數法,思路很簡單,但是如果出現循環引用,即:A 引用 B,B 又引用 A,這種情況下就不好辦了,所以 JVM 中使用了另一種稱為“可達性分析”的判斷方法:
還是剛才的循環引用問題(也是某些公司面試官可能會問到的問題),如果 A 引用 B,B 又引用 A,這 2 個對象是否能被 GC 回收?
答案:關鍵不是在于 A、B 之間是否有引用,而是 A、B 是否可以一直向上追溯到 GC Roots。如果與 GC Roots 沒有關聯,則會被回收,否則將繼續存活。
上圖是一個用“可達性分析”標記垃圾對象的示例圖,灰色的對象表示不可達對象,將等待回收。
2 哪些內存區域需要 GC ?
在第一部分 JVM 內存布局中,我們知道了 thread 獨享的區域:PC Regiester、JVM Stack、Native Method Stack,其生命周期都與線程相同(即:與線程共生死),所以無需 GC。線程共享的 Heap 區、Method Area 則是 GC 關注的重點對象。
3 常用的 GC 算法
1)mark-sweep 標記清除法
如上圖,黑色區域表示待清理的垃圾對象,標記出來后直接清空。該方法簡單快速,但是缺點也很明顯,會產生很多內存碎片。
2)mark-copy 標記復制法
思路也很簡單,將內存對半分,總是保留一塊空著(上圖中的右側),將左側存活的對象(淺灰色區域)復制到右側,然后左側全部清空。避免了內存碎片問題,但是內存浪費很嚴重,相當于只能使用 50% 的內存。
3)mark-compact 標記 - 整理(也稱標記 - 壓縮)法
避免了上述兩種算法的缺點,將垃圾對象清理掉后,同時將剩下的存活對象進行整理挪動(類似于 windows 的磁盤碎片整理),保證它們占用的空間連續,這樣就避免了內存碎片問題,但是整理過程也會降低 GC 的效率。
4)generation-collect 分代收集算法
上述三種算法,每種都有各自的優缺點,都不完美。在現代 JVM 中,往往是綜合使用的,經過大量實際分析,發現內存中的對象,大致可以分為兩類:有些生命周期很短,比如一些局部變量 / 臨時對象,而另一些則會存活很久,典型的比如 websocket 長連接中的 connection 對象,如下圖:
縱向 y 軸可以理解分配內存的字節數,橫向 x 軸理解為隨著時間流逝(伴隨著 GC),可以發現大部分對象其實相當短命,很少有對象能在 GC 后活下來。因此誕生了分代的思想,以 Hotspot 為例(JDK 7):
將內存分成了三大塊:年青代(Young Genaration),老年代(Old Generation), 永久代(Permanent Generation),其中 Young Genaration 更是又細為分 eden,S0,S1 三個區。
結合我們經常使用的一些 jvm 調優參數后,一些參數能影響的各區域內存大小值,示意圖如下:
注:jdk8 開始,用 MetaSpace 區取代了 Perm 區(永久代),所以相應的 jvm 參數變成 -XX:MetaspaceSize 及 -XX:MaxMetaspaceSize。
以 Hotspot 為例,我們來分析下 GC 的主要過程:
剛開始時,對象分配在 eden 區,s0(即:from)及 s1(即:to)區,幾乎是空著。
隨著應用的運行,越來越多的對象被分配到 eden 區。
當 eden 區放不下時,就會發生 minor GC(也被稱為 young GC),第 1 步當然是要先標識出不可達垃圾對象(即:下圖中的黃色塊),然后將可達對象,移動到 s0 區(即:4 個淡藍色的方塊挪到 s0 區),然后將黃色的垃圾塊清理掉,這一輪過后,eden 區就成空的了。
注:這里其實已經綜合運用了“【標記 - 清理 eden】 + 【標記 - 復制 eden->s0】”算法。
隨著時間推移,eden 如果又滿了,再次觸發 minor GC,同樣還是先做標記,這時 eden 和 s0 區可能都有垃圾對象了(下圖中的黃色塊),注意:這時 s1(即:to)區是空的,s0 區和 eden 區的存活對象,將直接搬到 s1 區。然后將 eden 和 s0 區的垃圾清理掉,這一輪 minor GC 后,eden 和 s0 區就變成了空的了。
繼續,隨著對象的不斷分配,eden 空可能又滿了,這時會重復剛才的 minor GC 過程,不過要注意的是,這時候 s0 是空的,所以 s0 與 s1 的角色其實會互換,即:存活的對象,會從 eden 和 s1 區,向 s0 區移動。然后再把 eden 和 s1 區中的垃圾清除,這一輪完成后,eden 與 s1 區變成空的,如下圖。
對于那些比較“長壽”的對象一直在 s0 與 s1 中挪來挪去,一來很占地方,而且也會造成一定開銷,降低 gc 效率,于是有了“代齡 (age)”及“晉升”。
對象在年青代的 3 個區 (edge,s0,s1) 之間,每次從 1 個區移到另 1 區,年齡 +1,在 young 區達到一定的年齡閾值后,將晉升到老年代。下圖中是 8,即:挪動 8 次后,如果還活著,下次 minor GC 時,將移動到 Tenured 區。
下圖是晉升的主要過程:對象先分配在年青代,經過多次 Young GC 后,如果對象還活著,晉升到老年代。
如果老年代,最終也放滿了,就會發生 major GC(即 Full GC),由于老年代的的對象通常會比較多,因為標記 - 清理 - 整理(壓縮)的耗時通常會比較長,會讓應用出現卡頓的現象,這也是為什么很多應用要優化,盡量避免或減少 Full GC 的原因。
注:上面的過程主要來自 oracle 官網的資料,但是有一個細節官網沒有提到,如果分配的新對象比較大,eden 區放不下,但是 old 區可以放下時,會直接分配到 old 區(即沒有晉升這一過程,直接到老年代了)。
下圖引自阿里出品的《碼出高效 -Java 開發手冊》一書,梳理了 GC 的主要過程。
以上就是JVM中GC垃圾回收原理是什么,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。