背景:看完《深入理解Java虛擬機》和相關(guān)博客,對JVM還是沒有一個條理清晰的認識,遂提取了書中相關(guān)知識點和參考相關(guān)優(yōu)秀博客并整理成JVM專題博文系列,幫助自己鞏固并理清有關(guān)JVM的知識重點,也分享出來給有需要的童鞋,如有差錯,歡迎拍磚!
在之前,我總結(jié)了JVM之內(nèi)存結(jié)構(gòu)中提到各種垃圾回收器,今天就來聊聊它們的回收策略
我們知道JAVA最大的優(yōu)點就是可以實現(xiàn)自動內(nèi)存管理,這極大的便利了JAVA程序員,降低了使用成本。但這也使得平時我們在使用JAVA編程時不太關(guān)注JVM到底是怎樣進行內(nèi)存回收的,只有在需要實際對JVM進行系統(tǒng)性能調(diào)優(yōu),這里的場景可能是在系統(tǒng)面臨極致性能優(yōu)化要求時,我們才發(fā)現(xiàn)需要對JAVA的整體內(nèi)存結(jié)構(gòu)以及內(nèi)存回收機制要有一定的認識和了解才行。
JVM垃圾回收
GC (Garbage Collection)的基本原理:將內(nèi)存中不再被使用的對象進行回收,GC中用于回收的方法稱為收集器,由于GC需要消耗一些資源和時間,Java在對對象的生命周期特征進行分析后,按照新生代、舊生代的方式來對對象進行收集,以盡可能的縮短GC對應用造成的暫停
對新生代的對象的收集稱為minor GC;
對舊生代的對象的收集稱為Full GC;
程序中主動調(diào)用System.gc()強制執(zhí)行的GC為Full GC。
不同的對象引用類型, GC會采用不同的方法進行回收,JVM對象的引用分為了四種類型:
強引用:默認情況下,對象采用的均為強引用(這個對象的實例沒有其他對象引用,GC時才會被回收)
軟引用:軟引用是Java中提供的一種比較適合于緩存場景的應用(只有在內(nèi)存不夠用的情況下才會被GC)
弱引用:在GC時一定會被GC回收
虛引用:由于虛引用只是用來得知對象是否被GC
在圖中,我們也大致對整個垃圾回收系統(tǒng)進行了標注,這里主要涉及回收策略、回收算法、垃圾回收器這幾個部分。形象一點表述,就是JVM需要知道那些內(nèi)存可以被回收,要有一套識別機制,在知道那些內(nèi)存可以回收以后具體采用什么樣的回收方式,這就需要設(shè)計一些回收算法,而具體的垃圾回收器就是根據(jù)不同內(nèi)存區(qū)域的使用特點,采用相應地回收策略和算法的具體實現(xiàn)了。
我們也標注了不同垃圾回收器所適用的特定內(nèi)存區(qū)域,對于JVM垃圾回收這塊的優(yōu)化,就是我們需要在了解這些垃圾回收算法、垃圾回收器特點后能夠根據(jù)自己應用的場景選擇合適的垃圾收集器,以及各區(qū)域垃圾收集器的搭配關(guān)系。
回收策略
我們知道,JVM進行內(nèi)存回收的主要目的是為了回收不再使用的內(nèi)存,因為在進行JAVA程序編寫時,我們只有new的操作,而不需要收工釋放不再使用的空間,如果這些空閑內(nèi)存不能及時被回收,很快我們的JVM內(nèi)存空間就會泄露(新申請內(nèi)存空間的操作失敗,導致程序報錯),所以回收不再使用的內(nèi)存的目的則是為了及時釋放空間,騰籠換鳥,以防止內(nèi)存泄漏。
那么問題來了,JAVA程序申請了那么多的內(nèi)存空間,那些內(nèi)存才能被認定是不再使用的內(nèi)存呢?搞錯了,如果把正在被程序使用的內(nèi)存給釋放了,程序邏輯就空指針異常了!
由于程序計數(shù)器、Java虛擬機棧、本地方法棧都是線程獨享,其占用的內(nèi)存也是隨線程生而生、隨線程結(jié)束而回收。而Java堆和方法區(qū)則不同,線程共享,是GC的所關(guān)注的部分。
在堆中幾乎存在著所有對象,GC之前需要考慮哪些對象還活著不能回收,哪些對象已經(jīng)死去可以回收,所以回收對象所占用的內(nèi)存是JAVA垃圾回收的主要目標。
有兩種算法可以判定對象是否存活:
引用計數(shù)算法:給對象中添加一個引用計數(shù)器,每當一個地方應用了對象,計數(shù)器加1;當引用失效,計數(shù)器減1;當計數(shù)器為0表示該對象已死、可回收。但是它很難解決兩個對象之間相互循環(huán)引用的情況。
可達性分析算法:通過一系列稱為“GC Roots”的對象作為起點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(即對象到GC Roots不可達),則證明此對象已死、可回收。Java中可以作為GC Roots的對象包括:虛擬機棧中引用的對象、本地方法棧中Native方法引用的對象、方法區(qū)靜態(tài)屬性引用的對象、方法區(qū)常量引用的對象。
那么如何判斷對象是處于可回收狀態(tài)的呢?在主流的JVM中是采用“可達性分析算法”來進行判斷的。
這個算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,并從這些節(jié)點開始往下進行搜索,搜索走過的路徑我們稱之為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,我們就稱之為對象引用不可達,則證明這個對象是不可用的,就可以暫時判定這個對象為可回收對象。示意圖如下:

在圖中雖然Obj F與Obj J之間互相有關(guān)聯(lián)但是它們到GC Roots是不可達的,所以將會被判定為可回收對象。既然如此,什么樣的對象可以作為GC Roots對象呢?
在JAVA中可以被作為GC Roots的對象主要是:虛擬機棧-棧幀中的本地變量表所引用的對象、方法區(qū)(<JDK1.8)中類靜態(tài)屬性所引用的對象/常量屬性所引用的對象、本地方法棧中引用的對象。
這里還需要注意一個小的細節(jié),就是被判定為對象不可達的對象也并非會被立刻回收,在學習JAVA語法是我們應該學習過finalize()方法,如果對象重寫了finalize方法,并重新把this關(guān)鍵字賦值給了某個類變量或?qū)ο蟮某蓡T變量的話,該對象就會被"救活",具體過程可參考上圖所示,只是這種方式并不鼓勵大家使用,了解下就行。
在關(guān)于如何判定對象是否屬于不再使用的內(nèi)存時,還有個通常會被大家錯誤認為是JVM使用的方式-“引用計數(shù)法”,事實上引用計數(shù)法的實現(xiàn)比較簡單,判定效率也比較高,在Python語言中就使用了這種算法進行內(nèi)存管理,但是它有一個比較難解決的對象之間循環(huán)引用的問題,所以在JAVA虛擬機里并沒有選用“引用計數(shù)法”來管理內(nèi)存,需要大家注意下!
參考
技術(shù)討論 & 疑問建議 & 個人博客
版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 CC BY-NC-SA 3.0 許可協(xié)議,轉(zhuǎn)載請注明出處!