JVM垃圾回收機制之GC解讀
JVM的垃圾回收機制:GC,是Java提供的對于內存自動回收的機制。
在 Java 中,所有的對象都是要存在內存中的(也可以說內存中存儲的是一個個對象),因此將內存回收,也可以叫做死亡對象的回收。GC回收的是“堆上的內存”。
一、死亡對象的判斷算法
1.1 引用計數算法
思想:
給對象增加一個引用計數器,每當有一個地方引用它時,計數器就+1;當引用失效時,計數器就-1; 任何時刻計數器為0的對象就是不能再被使用的,即對象已"死"。
引用計數法實現簡單,判定效率也比較高,在大部分情況下都是一個不錯的算法。比如Python語言就采用引用計數法進行內存管理。
在主流的JVM中沒有選用引用計數法來管理內存,最主要的原因就是引用計數法無法解決對象的循環(huán)引用問題。
1.2 可達性分析算法
思想:
通過一系列稱為"GC Roots"的對象作為起始點,從這些節(jié)點開始向下搜索,搜索走過的路徑稱之為"引用鏈",當一個對象到 GC Roots 沒有任何的引用鏈相連時 (從GC Roots到這個對象不可達)時,證明此對象是不可用的。如下:
對象 Object5 - Object7 之間雖然彼此還有關聯,但是它們到 GC Roots 是不可達的,因此他們會被判定為可回收對象。
在Java語言中,可作為 GC Roots 的對象包含下面幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象;
- 方法區(qū)中類靜態(tài)屬性引用的對象;
- 方法區(qū)中常量引用的對象;
- 本地方法棧中 JNI(Native方法)引用的對象。
在 JDK1.2 時,Java 對引用的概念做了擴充,分為以下四種,這四種引用的強度依次遞減:
- 強引用 : 強引用指的是在程序代碼之中普遍存在的,類似于"Object obj = new Object()"這類的引用,只要強引用還存在,垃圾回收器永遠不會回收掉被引用的對象實例。
- 軟引用 : 軟引用是用來描述一些還有用但是不是必須的對象。對于軟引用關聯著的對象,在系統將要發(fā)生內存溢出之前,會把這些對象列入回收范圍之中進行第二次回收。如果這次回收還是沒有足夠的內存,才會拋出內存溢出異常。在JDK1.2之后,提供了SoftReference類來實現軟引用。
- 弱引用 : 弱引用也是用來描述非必需對象的。但是它的強度要弱于軟引用。被弱引用關聯的對象只能生存到下一次垃圾回收發(fā)生之前。當垃圾回收器開始進行工作時,無論當前內容是否夠用,都會回收掉只被弱引用關聯的對象。在JDK1.2之后提供了WeakReference類來實現弱引用。
- 虛引用 : 虛引用也被稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。在JDK1.2之后,提供了PhantomReference類來實現虛引用。
二、垃圾回收算法
2.1 標記-清除算法
"標記-清除"算法是最基礎的收集算法。算法分為"標記"和"清除"兩個階段 : 首先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象。后續(xù)的收集算法都是基于這種思路并對其不足加以改進而已。
- 效率問題 : 標記和清除這兩個過程的效率都不高。
- 空間問題 : 標記清除后會產生大量不連續(xù)的內存碎片,空間碎片太多可能會導致以后在程序運行中需要分配較大對象時,無法找到足夠連續(xù)內存而不得不提前觸發(fā)另一次垃圾收集。
2.2 復制算法
復制"算法是為了解決"標記-清理"的效率問題。
它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這塊內存需要進行垃圾回收時,會將此區(qū)域還存活著的對象復制到另一塊上面,然后再把已經使用過的內存區(qū)域一次清理掉。這樣做的好處是每次都是對整個半區(qū)進行內存回收,內存分配時也就不需要考慮內存碎片等復雜情況,只需要移動堆頂指針,按順序分配即可。
此算法實現簡單,運行高效。算法的執(zhí)行流程如下圖 :
2.3 標記-整理算法
復制收集算法在對象存活率較高時會進行比較多的復制操作,效率會變低。因此在老年代一般不能使用復制算法。 針對老年代的特點,提出了一種稱之為"標記-整理算法"。標記過程仍與"標記-清除"過程?致,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活對象都向一端移動,然后直接清理掉端邊界以外的內存。
流程圖如下:
2.4 分代算法
分代算法和上面 3 種算法不同,分代算法是通過區(qū)域劃分,實現不同區(qū)域和不同的垃圾回收策略,從而實現更好的垃圾回收。
當前 JVM 垃圾收集都采用的是"分代收集(Generational Collection)"算法,這個算法并沒有新思想,只是根據對象存活周期的不同將內存劃分為幾塊。
一般是把Java堆分為新生代和老年代。在新生代中,每次垃圾回收都有大批對象死去,只有少量存活,因此我們采用復制算法;而老年代中對象存活率高、沒有額外空間對它進行分配擔保,就必須采用"標記-清理"或者"標記-整理"算法。
哪些對象會進入新生代?哪些對象會進入老年代?
- 新生代:一般創(chuàng)建的對象都會進入新生代;
- 老年代:大對象和經歷了 N 次(一般情況默認是 15 次)垃圾回收依然存活下來的對象會從新生代移動到老年代。
面試題 : 請問了解Minor GC和Full GC么,這兩種GC有什么不一樣嗎?
- Minor GC 又稱為新生代GC : 指的是發(fā)生在新生代的垃圾收集。因為Java對象大多都具備朝生夕滅的特性,因此Minor GC(采用復制算法)非常頻繁,一般回收速度也比較快。
- Full GC 又稱為老年代GC 或者 Major GC : 指發(fā)生在老年代的垃圾收集。出現了Major GC,經常會伴隨至少一次的Minor GC (并非絕對,在Parallel Scavenge收集器中就有直接進行Full GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。
三、垃圾收集器
收集算法是內存回收的方法論,垃圾收集器是內存回收的具體實現。
垃圾收集器的作用:垃圾收集器是為了保證程序能夠正常、持久運行的一種技術,它是將程序中不用的死亡對象也就是垃圾對象進行清除,從而保證了新對象能夠正常申請到內存空間。
以下這些收集器是 HotSpot 虛擬機隨著不同版本推出的重要的垃圾收集器:
上圖展示了7種作用于不同分代的收集器,如果兩個收集器之間存在連線,就說明他們之間可以搭配使用。所處的區(qū)域,表示它是屬于新生代收集器還是老年代收集器。
- 并行(Parallel) : 指多條垃圾收集線程并行工作,用戶線程仍處于等待狀態(tài)。
- 并發(fā)(Concurrent) : 指用戶線程與垃圾收集線程同時執(zhí)行 (不一定并行,可能會交替執(zhí)行),用戶程序繼續(xù)運行,而垃圾收集程序在另外一個CPU上。
- 吞吐量:就是CPU用于運行用戶代碼的時間與CPU總消耗時間的比值。
- 吞吐量 = 運行用戶代碼時間/(運行用戶代碼時間 + 垃圾收集時間)
例如:虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
3.1 CMS收集器(老年代收集器,并發(fā)GC)
特性:
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就非常符合這類應用的需求。
CMS收集器是基于“標記—清除”算法實現的,它的整個過程分為4個步驟:
- 1. 初始標記(CMS initial mark):初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,需要“Stop The World”。
- 2. 并發(fā)標記(CMS concurrent mark):并發(fā)標記階段就是進行GC Roots Tracing的過程。
- 3. 重新標記(CMS remark):重新標記階段是為了修正并發(fā)標記期間因用戶程序繼續(xù)運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比并發(fā)標記的時間短,仍然需要“Stop The World”。
- 4. 并發(fā)清除(CMS concurrent sweep):并發(fā)清除階段會清除對象。 由于整個過程中耗時最長的并發(fā)標記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作,所以,從總體上來說,CMS收集器的內存回收過程是與用戶線程一起并發(fā)執(zhí)行的。
優(yōu)點:
- CMS是一款優(yōu)秀的收集器,它的主要優(yōu)點在名字上已經體現出來了:并發(fā)收集、低停頓。
缺點:
- CMS收集器對CPU資源非常敏感;
- CMS收集器無法處理浮動垃圾;
- CMS收集器會產生大量空間碎片。
3.2 G1收集器(唯一一款全區(qū)域的垃圾回收器)
G1(Garbage First)垃圾回收器是用在heap memory很大的情況下,把heap劃分為很多很多的 region塊,然后并行的對其進行垃圾回收。
G1垃圾回收器在清除實例所占用的內存空間后,還會做內存壓縮。
年輕代垃圾收集 :
在G1垃圾收集器中,年輕代的垃圾回收過程使用復制算法。把Eden區(qū)和Survivor區(qū)的對象復制到新的Survivor區(qū)域。 如下圖:
老年代垃收集:
- 對于老年代上的垃圾收集,G1垃圾收集器也分為4個階段,基本跟CMS垃圾收集器一樣,但略有不同:
- 初始標記(Initial Mark)階段:同CMS垃圾收集器的Initial Mark階段一樣,G1也需要暫停應用程序的執(zhí)行,它會標記從根對象出發(fā),在根對象的第一層孩子節(jié)點中標記所有可達的對象。但是G1的垃圾收集器的Initial Mark階段是跟minor gc一同發(fā)生的。也就是說,在G1中,你不用像在CMS那樣,單獨暫停應用程序的執(zhí)行來運行Initial Mark階段,而是在G1觸發(fā)minor gc的時候一并將年老代上的Initial Mark給做了。
- 并發(fā)標記(Concurrent Mark)階段:在這個階段G1做的事情跟CMS一樣。但G1同時還多做了一件事情,就是如果在Concurrent Mark階段中,發(fā)現哪些Tenured region中對象的存活率很小或者基本沒有對象存活,那么G1就會在這個階段將其回收掉,而不用等到后面的clean up階段。這也是Garbage First名字的由來。同時,在該階段,G1會計算每個 region的對象存活率,方便后面的clean up階段使用 。
- 最終標記(CMS中的Remark階段):在這個階段G1做的事情跟CMS一樣, 但是采用的算法不同,G1采用一種叫做SATB(snapshot-at-the-begining)的算法能夠在Remark階段更快的標記可達對象。
- 篩選回收(Clean up/Copy)階段:在G1中,沒有CMS中對應的Sweep階段。相反,它有一個Clean up/Copy階段,在這個階段中,G1會挑選出那些對象存活率低的region進行回收,這個階段也是和minor gc一同發(fā)生的,如下圖所示:
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
SpringBoot集成Aviator實現參數校驗的代碼工程
Aviator是一個高性能、輕量級的java語言實現的表達式求值引擎,主要用于各種表達式的動態(tài)求值,本文給大家詳細介紹了SpringBoot集成Aviator實現參數校驗的方法,并通過代碼示例講解的非常詳細,需要的朋友可以參考下2024-11-11IDEA創(chuàng)建web項目出現404錯誤解決方法
今天先來搭建一個web工程,工程搭建好運行時發(fā)現404,本文主要介紹了IDEA創(chuàng)建web項目出現404錯誤解決方法,具有一定的參考價值,感興趣的可以了解一下2023-09-09Spring Boot 整合mybatis 與 swagger2
之前使用springMVC+spring+mybatis,總是被一些繁瑣的xml配置,還經常出錯,下面把以前的一些ssm項目改成了spring boot + mybatis,相對于來說優(yōu)點太明顯了,具體內容詳情大家通過本文學習吧2017-08-08