首頁  >  文章  >  Java  >  jvm的垃圾回收機制是什麼

jvm的垃圾回收機制是什麼

青灯夜游
青灯夜游原創
2023-02-01 14:02:2611978瀏覽

jvm的垃圾回收機制是GC(Garbage Collection),也叫垃圾收集器。 GC基本原理:將記憶體中不再被使用的物件回收;GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對物件的生命週期特徵進行分析後,按照新生代、老年代的方式來對物件進行收集,以盡可能的縮短GC對應用造成的暫停。

jvm的垃圾回收機制是什麼

本教學操作環境:windows7系統、java8版、DELL G3電腦。

什麼是垃圾回收

java相較於c、c 語言的優點之一是自備垃圾回收器,垃圾回收是指不定時去堆內存中清理不可達物件。不可達到的物件不會馬上就會直接回收, 垃圾收集器在一個Java程式中的執行是自動的,不能強制執行,程式設計師唯一能做的就是透過呼叫System.gc 方法來建議執行垃圾收集器,但其是否可以執行,什麼時候執行卻都是不可知的。這也是垃圾收集器的最主要的缺點。當然相對於它帶給程式設計師的巨大便利性而言,這個缺點是瑕不掩瑜的。

為什麼需要垃圾回收

如果不進行垃圾回收,記憶體遲早都會被消耗空,因為我們在不斷的分配記憶體空間而不進行回收。除非記憶體無限大,我們可以任性的分配而不回收,但事實並非如此。所以,垃圾回收是必須的。

jvm垃圾回收原理

在JVM運行時資料區存在一個堆區, 堆是一個巨大的物件池。在這個物件池中管理著數量龐大的物件實例,而池中物件的參考層次,有的是很深的。一個被頻繁調用的接口,每秒生成對象的速度,是很大的,同時,對象之間的關係,形成了一張巨大的網。

Java 一直在營造一種無限記憶體的氛圍,但物件不能只增不減,所以需要垃圾回收;那 JVM 是如何判斷哪些物件應該被回收?哪些應該被保持?這就要用到JVM的垃圾回收機制了,也就是我們常說的GC(Garbage Collection),也叫垃圾收集器。

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的記憶體結構包含五大區域:

程式計數器虛擬機器堆疊本機方法堆疊堆疊區方法區。其中程式計數器、虛擬機器棧、本地方法棧3個區域隨線程而生、隨線程而滅,因此這幾個區域的記憶體分配和回收都具備確定性,就不需要過多考慮回收的問題,因為方法結束或執行緒結束時,記憶體自然就跟著回收了。而Java堆區和方法區則不一樣,這部分記憶體的分配與回收是動態的,正是垃圾收集器所需關注的部分。

1. 引用計數器

參考計數是垃圾收集器中的早期策略。在這種方法中,堆中每個物件實例都有一個參考計數。當一個物件被建立時,就將該物件實例指派給一個變量,該變數計數設為1。當任何其它變數被賦值為這個物件的引用時,計數加1(a = b,則b引用的物件實例的計數器1),但當一個物件實例的某個引用超過了生命週期或被設定為一個新值時,物件實例的引用計數器減1。任何引用計數器為0的物件實例可以被當作垃圾收集。當一個物件實例被垃圾收集時,它所引用的任何物件實例的參考計數器會減1。

優點: 引用計數收集器可以很快的執行,交織在程式運作中。對程式需要不被長時間打斷的即時環境比較有利。
缺點: 無法偵測出循環引用。如父物件有一個子物件的引用,子物件反過來引用父物件。這樣,他們的引用計數永遠不可能是0。

jvm的垃圾回收機制是什麼

對上述程式碼透過引用計數法分析:

jvm的垃圾回收機制是什麼

jvm的垃圾回收機制是什麼

3-jvm的垃圾回收機制是什麼

2. 可達性分析

可達性演算法是目前主流的虛擬機都採用的演算法,程序把所有的引用關係看作一張圖,從一個節點GC Roots開始,尋找對應的引用節點,找到這個節點以後,繼續尋找這個節點的引用節點,當所有的引用節點尋找完畢之後,剩餘的節點則被認為是沒有被引用到的節點,即無用的節點,無用的節點將會被判定為是可回收的物件。

在Java語言中,可作為GC Roots的物件包含以下幾種:
● 虛擬機器堆疊中所引用的物件(堆疊框架中的本機變數表);
● 方法區中類別靜態屬性所引用的物件;
● 方法區中常數引用的物件;
● 本地方法堆疊中JNI(Native方法)所引用的物件。

jvm的垃圾回收機制是什麼

可以得到物件實例1、2、4、6都具有物件可達性,也就是存活對象,不能被GC回收的物件。而隨想實例3、5雖然直接相連,但並沒有任何一個GC Roots與之相連,即GC Roots不可達對象,就會被GC回收的對象。

三、垃圾回收演算法

1. 標記-清除演算法

標記/清除演算法的基本想法就跟它的名字一樣,分為「標記」和「清除」兩個階段:首先標記所有需要回收的對象,在標記完成後統一回收所有被標記的對象。
標記階段:
標記的過程其實就是前面介紹的可達性分析演算法的過程,遍歷所有的GC Roots 對象,對從GCRoots 物件可達的物件都打上一個標識,一般是在物件的header 中,將其記錄為可達物件。
清除階段:
清除的過程是對堆記憶體進行遍歷,如果發現某個物件沒有被標記為可達物件(透過讀取物件header 訊息),則將其回收。

jvm的垃圾回收機制是什麼

上圖是標記/清除演算法的示意圖,在標記階段,從對象GC Root 1 可以存取B 對象,從B 對象又可以存取E 對象,因此從GC Root 1 到B、E 都是可達的,同理,物件F、G、J、K 都是可達物件;到了清除階段,所有不可達物件都會被回收。
在垃圾收集器進行GC 時,必須停止所有Java 執行緒(也稱為"Stop The World"),原因是在標記階段進行可達性分析時,不可以出現分析過程中物件引用關係還在不斷變化的情況,否則的話可達性分析結果的準確性就無法得到保證。在等待標記清除結束後,應用程式執行緒才會恢復運作。

標記/清除演算法缺點:
效率問題
標記和清除兩個階段的效率都不高,因為這兩個階段都需要遍歷內存中的對象,很多時候內存中的對象實例數量是非常龐大的,這無疑很耗費時間,而且GC 時需要停止應用程序,這會導致非常差的用戶體驗。
空間問題
標記清除之後會產生大量不連續的記憶體碎片(從上圖可以看出),記憶體空間碎片太多可能會導致以後在程式運行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾回收動作。

2. 複製演算法

複製演算法是將可用記憶體依容量分割成大小相等的兩塊,每次使用其中的一塊。當這一塊的記憶體用完了,就將還存活的物件複製到另一塊記憶體上,然後把這一塊記憶體所有的物件一次清理掉
jvm的垃圾回收機制是什麼

#

複製演算法每次都是對整個半區進行內存回收,這樣就減少了標記對象遍歷的時間,在清除使用區域對象時,不用進行遍歷,直接清空整個區域內存,而且在將存活對象複製到保留區域時也是按位址順序儲存的,這樣就解決了記憶體碎片的問題,在分配物件記憶體時不用考慮記憶體碎片等複雜問題,只需要按順序分配記憶體即可。

複製演算法缺點:
複製演算法簡單高效,優化了標記清除演算法的效率低、記憶體碎片多問題,存在缺點:
● 將記憶體縮小為原來的一半,浪費了一半的記憶體空間,代價太高;
● 如果物件的存活率很高,極端一點的情況假設物件存活率為100%,那麼我們需要將所有存活的物件複製一遍,耗費的時間代價也是不可忽視的。

3. 標記-整理演算法

標記-整理演算法演算法與標記/清除演算法很像,事實上,標記/整理演算法的標記過程任然與標記/清除演算法一樣,但後續步驟不是直接對可回收物件進行回收,而是讓所有存活的物件都向一端移動,然後直接清理掉端邊線以外的記憶體。
jvm的垃圾回收機制是什麼

可以看到,回收後可回收物件被清理掉了,存活的物件按規則排列存放在記憶體中。這樣一來,當我們給新物件分配記憶體時,jvm 只需要持有記憶體的起始位址即可。標記/整理演算法彌補了標記/清除演算法存在記憶體碎片的問題消除了複製演算法記憶體減半的高額代價,可謂一舉兩得。

標記/整理缺點:
● 效率不高:不僅要標記存活對象,還要整理所有存活對象的參考位址,在效率上不如複製演算法。

4. 分代回收演算法

分代收集演算法的想法是按物件的存活週期不同將記憶體劃分為幾塊一般是把Java 堆分為新生代和老年代(還有一個永久代,是HotSpot 特有的實現,其他的虛擬機實現沒有這一概念,永久代的收集效果很差,一般很少對永久代進行垃圾回收),這樣就可以根據各個年代的特徵採用最適合的收集演算法。

特色:
新生代:朝生夕滅,存活時間很短。採用複製演算法來收集
老年代:經過多次 Minor GC 而存活下來,存活週期長。採用標記/清除演算法或標記/整理演算法收集老年代

新生代中每次垃圾回收都發現有大量的物件死去,只有少量存活,因此採用複製演算法回收新生代,只需要付出少量物件的複製成本就可以完成收集;
老年代中物件的存活率高,不適合採用複製演算法,而且如果老年代採用複製演算法,它是沒有額外的空間進行分配擔保的,因此必須使用標記/清理演算法或標記/整理演算法來進行回收。

jvm的垃圾回收機制是什麼

新生代中的物件“朝生夕死”,每次GC時都會有大量物件死去,少量存活,使用複製演算法。新生代又分為Eden區和Survivor區(Survivor from、Survivor to),大小比例預設為8:1:1。
老年代中的物件因為物件存活率高、沒有額外空間進行分配擔保,就使用標記-清除或標記-整理演算法。
新產生的物件優先進去Eden區,當Eden區滿了之後再使用Survivor from,當Survivor from 也滿了之後就進行Minor GC(新生代GC),將Eden和Survivor from中存活的物件copy進入Survivor to,然後清空Eden和Survivor from,這個時候原來的Survivor from成了新的Survivor to,原來的Survivor to成了新的Survivor from。複製的時候,如果Survivor to 無法容納全部存活的對象,則根據老年代的分配擔保(類似於銀行的貸款擔保)將對象copy進去老年代,如果老年代也無法容納,則進行Full GC(老年代GC)。
大物件直接進入老年代:JVM中有個參數配置
-XX:PretenureSizeThreshold,令大於這個設定值的物件直接進入舊年代,目的是為了避免在Eden和Survivor區之間發生大量的記憶體複製。
長期存活的物件進入老年代:JVM為每個物件定義一個物件年齡計數器,如果物件在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納,將被移入Survivor並且年齡設定為1。沒撐過一次Minor GC,年齡就加1,當他的年齡到一定程度(預設為15歲,可以透過XX:MaxTenuringThreshold來設定),就會移入老年代。但JVM並不是永遠要求年齡必須達到最大年齡才會晉升老年代,如果Survivor 空間中相同年齡(如年齡為x)所有物件大小的總和大於Survivor的一半,年齡大於等於x的所有物件直接進入老年代,無需等到最大年齡要求。

分代回收:

我們從一個object1來說明其在分代垃圾回收演算法中的回收軌跡。

1、object1新建,出生於新生代的Eden區域。
jvm的垃圾回收機制是什麼
2、minor GC,object1 仍存活,移動到From suvivor空間,此時仍在新生代。

jvm的垃圾回收機制是什麼

3、minor GC,object1 仍然存活,此時會透過複製演算法,將object1移到ToSuv區域,此時object1的年齡age 1。
jvm的垃圾回收機制是什麼
4、minor GC,object1 仍然存活,此時survivor中和object1同齡的物件並沒有達到survivor的一半,所以此時透過複製演算法,將fromSuv和Tosuv 區域進行互換,存活的物件被移到了Tosuv。
jvm的垃圾回收機制是什麼
5、minor GC,object1 仍然存活,此時survivor中和object1同齡的對像已經達到survivor的一半以上(toSuv的區域已經滿了),object1被移動到了老年代區域。
jvm的垃圾回收機制是什麼
6、object1存活一段時間後,發現此時object1不可達GcRoots,而且此時老年代空間比率已經超過了閾值,觸發了majorGC(也可以認為是fullGC,但具體需要垃圾收集器來聯繫),此時object1被回收了。 fullGC會觸發 stop the world。
jvm的垃圾回收機制是什麼
在以上的新生代中,我們有提到對象的age,對象存活於survivor狀態下,不會立即晉升為老年代對象,以避免給老年代造成過大的影響,它們必須要滿足以下條件才可以晉升:
1、minor gc 之後,存活於survivor 區域的對象的age會1,當超過(默認)15的時候,轉移到老年代。
2.動態對象,如果survivor空間中相同年齡所有的對像大小的綜合和大於survivor空間的一半,年級大於或等於該年紀的對象就可以直接進入老年代。

以上是jvm的垃圾回收機制是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn