Java基礎之垃圾回收機制詳解
一、GC的作用
進行內存管理
C語言中的內存,申請內存之后需要手動釋放;一旦忘記釋放,就會發(fā)生內存泄漏!
而Java語言中,申請內存后會由GC來釋放內存空間,無需手動釋放
GC雖然代替了手動釋放的操作,但是它也有局限性:
- 需要消耗更多的資源;
- 沒有手動釋放那么及時;
- STW(Stop The World)會影響程序的執(zhí)行效率
二、GC主要回收哪些內存
(1)堆:主要回收堆中的內存
(2)方法區(qū):需要回收
(3)棧(包括本地方法棧和JVM虛擬機棧):不需要回收,棧上的內存什么時候釋放是明確的(線程結束,棧上的內存也就被釋放了;對應的某個棧幀銷毀[某個方法執(zhí)行完畢],也會導致對應的局部變量被釋放)
(4)程序計數器:不需要被回收
GC回收內存的基本單位:對象
GC回收對象的基本思路
(1)標記:判斷當前對象的生死,對象不再被使用為死,則需要回收,反之不需要被回收;
標記的方法:
- 引用計數法
記錄當前這個對象是否有引用指向,有則引用計數加1,如果當前這個對象的引用指向了其他新的對象,則引用計數減1,當引用計數為0的時候,我們認為這個對象需要被回收!
缺點:無法解決循環(huán)引用問題
下面用一段偽代碼來演示一下循環(huán)引用問題:
class Test{ Test t = null; } Test a = new Test(); Test b = new Test(); a.t = b; b.t = a; a = null; b = null;
我們發(fā)現(xiàn),在上述代碼中已經沒有辦法使用對象a和對象b了,但是它們的引用計數不為1.想使用對象a,就得找到對象a的引用,但是對象a的引用又在對象b當中。想使用對象b,就得找到對象b 的引用,但是對象b的引用又在對象a當中。
- 可達性分析:
代碼中的對象具有一定的關聯(lián)關系,這樣錯綜復雜的關系,構成了一個"有向圖"??蛇_性分析也就是遍歷這個對象關系的“有向圖”。如果某個對象可以被遍歷到,那么它就是可達的(非垃圾),那么就是不可達的(是垃圾)
那么可達性分析從哪里開始呢?
a)針對每個線程的每個棧幀的局部變量表(線程有很多,每個線程棧幀也有很多,每個棧幀也會有很多個變量);
b)常量池中引用的對象;
c)方法區(qū)中靜態(tài)變量引用的對象;
因為遍歷的起點不止一個,而是很多個起點,因此把這些起點也稱之為GCRoot
- 回收方法區(qū)對象的規(guī)則:
a)該類的所有實例已經被回收;
b)加載類的ClassLoader也已經被回收了;
c)該類對象沒有在代碼中使用了
同時具備以上三個條件,就認為該類對象是可以被回收的
回收的方法:
- 標記-清除【適合老年代】
通過上面的圖,我們可以發(fā)現(xiàn),兩個空閑區(qū)被其他的對象分隔開了。一旦需要一個比較大的空間,就會申請失敗。
標記-清除法的優(yōu)缺點:
優(yōu)點:簡單高效
缺點:會出現(xiàn)內存碎片
- 標記-復制【適合新生代】
優(yōu)點:解決了內存碎片問題,保證回收之后不會存在碎片(回收后使用的對象之間是連續(xù)的,空余內存之間也是連續(xù)的)
缺點:需要一塊額外的空間,如果生存的對象較多就比較難低效
- 標記-整理【適合老年代】
優(yōu)點:沒有內存碎片問題,也不需要額外的空間
缺點:類似于順序表的刪除操作,效率不是很高
三、分代回收
按照對象的年齡,將堆內存分為:新生代(伊甸區(qū)和生存區(qū))、老年代
對象的年齡不是直接使用時間來記錄,而是使用對象活過GC輪次來記錄(GC是按照一定周期來運行)
一個對象的一生:
(1)對象誕生于新生代的伊甸區(qū)。新產生的對象的內存就是新生代中的內存
(2)第一輪GC掃描伊甸區(qū)之后,就會把大量的對象回收掉。少數沒有被回收的對象,就會通過標記-復制算法進入到生存區(qū)
(3)少數進入生存區(qū)的對象,再次被GC掃描(對這些對象進行可達性分析)。如果發(fā)現(xiàn)該對象已經不可達,也就被銷毀了。沒有被銷毀的對象,再次通過標記-復制算法,把它拷貝到另一個生存區(qū)。
(4)對象在兩個生存區(qū)中經過若干次拷貝,如果還沒有被回收,那么就說明這些個對象存活時間比較久,就拷貝到老年代
(5)老年代的對象也是要經過GC掃描的。由于老年代的對象生存時間比較長。因此掃描周期要比新生代的周期要長
相關術語:
- Partical GC:只進行一部分內存區(qū)域的GC
- Full GC:針對整個內存區(qū)域進行GC
- Minor GC:針對新生代內存的GC,執(zhí)行頻繁,速度較快
- Major GC:針對老年代的GC,沒那么頻繁。速度較慢,通常由Minor GC 觸發(fā)
四、垃圾回收器
垃圾回收器做的兩件事情:標記(可達性分析)+回收(標記清除,標記復制,標記整理)
- Serial收集器(給新生代使用,串行回收)【存在STW】
采用復制算法,單線程進行標記和回收
- ParNew收集器(新生代收集器,多線程GC)
采用復制算法,多線程進行標記和回收
- Parallel scavenge收集器(新生代收集器,并行GC)
設計初衷是為了縮短STW時間,以犧牲吞吐量和新生代空間作為代價。
相當于承諾用戶,在一定時間內就會完成一次GC。
- Serial Old收集器(老年代收集器,串行GC)
- Parallel old收集器(老年代收集器,并行GC)
使用多線程完成標記整理,效率更高,消耗的CPU資源更多
- CMS垃圾回收器(老年代收集器,并行GC,采用多線程標記清除算法)
a)初始標記【STW】
只是把和GCRoot相關的對象標記出來,涉及STW
b)并發(fā)標記
執(zhí)行整個標記遍歷的過程(從GCRoot開始,把能訪問的對象都遍歷)
不需要暫停用戶線程
消耗的時間相對比較久,但是可以和用戶線程并發(fā)
注意:當進行并發(fā)標記的時候,當用戶線程也在執(zhí)行,可能導致某個對象,剛剛標記的時候不是垃圾,代碼執(zhí)行后,就成了垃圾
c)重新標記(CMS remark)【STW】
修正誤差
d)并發(fā)清除
多線程的方式將剛剛的垃圾對象都清除釋放掉,可以和應用程序并發(fā)執(zhí)行
優(yōu)點:能夠讓STW時間盡量短
缺點:有內存碎片; GC操作和應用程序并發(fā)進行,消耗CPU資源多;
- G1回收器(Java11開始默認使用)
既可以回收新生代,也可以回收老年代
每個矩形稱為一個region
E表示伊甸區(qū)
S表示生存區(qū)
T表示老年代
H表示存放大對象的區(qū)域
以region為單位進行回收,回收粒度更精細
針對新生區(qū)的region同樣適用復制算法
針對老年代的回收類似于CMS
a)初始標記【STW】:只去找和GRoot直接相連的對象
b)并發(fā)標記:和應用程序并發(fā)執(zhí)行,進行可達性分析,遍歷所有對象。如果發(fā)現(xiàn)某個老年代region中已經沒有存活對象,就直接回收
c)最終標記:修正第二步產生的誤差
d)篩選回收:挑選出對象存活率低的region進行回收
五、總結
到此這篇關于Java基礎之垃圾回收機制詳解的文章就介紹到這了,更多相關Java垃圾回收機制內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Springboot整合quartz實現(xiàn)多個定時任務實例
這篇文章主要介紹了Springboot整合quartz實現(xiàn)多個定時任務代碼實例,Quartz?是一款功能強大的開源任務調度框架,幾乎可以集成到任何?Java?應用程序中,Quartz?可用于創(chuàng)建簡單或復雜的任務調度,用以執(zhí)行數以萬計的任務,需要的朋友可以參考下2023-08-08Java中零拷貝和深拷貝的原理及實現(xiàn)探究(代碼示例)
深拷貝和零拷貝是兩個在 Java 中廣泛使用的概念,它們分別用于對象復制和數據傳輸優(yōu)化,下面將詳細介紹這兩個概念的原理,并給出相應的 Java 代碼示例,感興趣的朋友一起看看吧2023-12-12詳解SpringBoot的三種緩存技術(Spring Cache、Layering Cache 框架、Alibaba J
這篇文章主要介紹了SpringBoot的三種緩存技術,幫助大家更好的理解和學習springboot框架,感興趣的朋友可以了解下2020-10-10