摘要:在這種消耗很高的狀態下,應用程序所有的線程都會掛起,暫停一切正常的工作,等待垃圾回收的完成。但是,因為線程切換和上下文轉換的消耗,會使得垃圾回收的總體成本上升,造成系統吞吐量的下降。
Java 垃圾回收(GC) 泛讀
0. 序言文章地址: https://segmentfault.com/a/1190000008922319
帶著問題去看待 垃圾回收(GC) 會比較好,一般來說主要的疑惑在于這么幾點:
為什么需要 GC ?
虛擬機(JVM) 與 垃圾回收(GC) 的關系?
GC 的原理有哪些?
哪些 對象容易被 GC ?
等等
帶著這些問題往下看:
1. 為什么需要 GC ?GC: 是Garbage Collection 的英文縮略,垃圾收集的意思。
為什么需要 GC?
主要是隨著應用程序所應對的業務越來越龐大、復雜,用戶越來越多,沒有GC就不能保證應用程序正常進行。
為什么經常討論 GC,沒有完美的解決方案嗎?
完美的解決方法目前還沒有。由于在 GC 時需要STW(Stop The World),這長不能滿足實際的需求,容易造成卡頓、延遲等性能問題,所以才會不斷地嘗試對GC進行優化。社區的需求是盡量減少對應用程序的正常執行干擾,這也是業界目標。
以 HotSpotJVM 為例描述下 GC 在 JVM 中的位置:
由于 不同的 JVM 會有不同的 GC 實現,不同的 GC 實現使用的算法又不盡相同,這才造成了 GC 的多樣性。
在收購SUN之前,Oracle使用的是JRockit JVM,收購之后使用HotSpot JVM。目前Oracle擁有兩種JVM實現并且一段時間后兩個JVM實現會合二為一。
HotSpot JVM是目前Oracle SE平臺標準核心組件的一部分。
最新的 GC 方案是 Garbage First(一般簡稱為 G1)。
1999年隨JDK1.3.1一起來的是串行方式的Serial GC ,它是第一款 GC 。
2002年2月26日,J2SE1.4發布,Parallel GC 和Concurrent Mark Sweep (CMS)GC跟隨JDK1.4.2一起發布,并且Parallel GC在JDK6之后成為HotSpot默認GC。
2. 不同 GC 的區別HotSpot有這么多的垃圾回收器,那么如果有人問,Serial GC、Parallel GC、Concurrent Mark Sweep GC這三個GC有什么不同呢?請記住以下口令:
如果你想要最小化地使用內存和并行開銷,請選Serial GC;
如果你想要最大化應用程序的吞吐量,請選Parallel GC;
如果你想要最小化GC的中斷或停頓時間,請選CMS GC。
當然這不包括新推出的 GC 方案----G1。
3. 關于 Java 1.7 之后的 G1為什么名字叫做Garbage First(G1)呢?
因為G1是一個并行回收器,它把堆內存分割為很多不相關的區間(Region),每個區間可以屬于老年代或者年輕代,并且每個年齡代區間可以是物理上不連續的。
老年代區間這個設計理念本身是為了服務于并行后臺線程,這些線程的主要工作是尋找未被引用的對象。而這樣就會產生一種現象,即某些區間的垃圾(未被引用對象)多于其他的區間。
垃圾回收時實則都是需要停下應用程序的,不然就沒有辦法防治應用程序的干擾 ,然后G1 GC可以集中精力在垃圾最多的區間上,并且只會費一點點時間就可以清空這些區間里的垃圾,騰出完全空閑的區間。
繞來繞去終于明白了,由于這種方式的側重點在于處理垃圾最多的區間,所以我們給G1一個名字:垃圾優先(Garbage First)。
4. GC 的原理 1. 對象存活判斷判斷對象是否存活一般有兩種方式:
引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。此方法簡單,無法解決對象相互循環引用的問題。
可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。不可達對象。
在Java語言中,GC Roots包括:虛擬機棧中引用的對象、方法區中類靜態屬性實體引用的對象、方法區中常量引用的對象、本地方法棧中JNI引用的對象。
2. GC 常用的算法及原理引用計數法 (Reference Counting)
引用計數器在微軟的 COM 組件技術中、Adobe 的 ActionScript3 種都有使用。
引用計數器的實現很簡單,對于一個對象 A,只要有任何一個對象引用了 A,則 A 的引用計數器就加 1,當引用失效時,引用計數器就減 1。只要對象 A 的引用計數器的值為 0,則對象 A 就不可能再被使用。
引用計數器的實現也非常簡單,只需要為每個對象配置一個整形的計數器即可。但是引用計數器有一個嚴重的問題,即無法處理循環引用(即兩個對象相互引用)的情況。因此,在 Java 的垃圾回收器中沒有使用這種算法。
一個簡單的循環引用問題描述如下:有對象 A 和對象 B,對象 A 中含有對象 B 的引用,對象 B 中含有對象 A 的引用。此時,對象 A 和對象 B 的引用計數器都不為 0。但是在系統中卻不存在任何第 3 個對象引用了 A 或 B。也就是說,A 和 B 是應該被回收的垃圾對象,但由于垃圾對象間相互引用,從而使垃圾回收器無法識別,引起內存泄漏。
標記-清除算法 (Mark-Sweep)
標記-清除算法將垃圾回收分為兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段首先通過根節點,標記所有從根節點開始的較大對象。因此,未被標記的對象就是未被引用的垃圾對象。然后,在清除階段,清除所有未被標記的對象。該算法最大的問題是存在大量的空間碎片,因為回收后的空間是不連續的。在對象的堆空間分配過程中,尤其是大對象的內存分配,不連續的內存空間的工作效率要低于連續的空間。
復制算法 (Copying)
將現有的內存空間分為兩快,每次只使用其中一塊,在垃圾回收時將正在使用的內存中的存活對象復制到未被使用的內存塊中,之后,清除正在使用的內存塊中的所有對象,交換兩個內存的角色,完成垃圾回收。
如果系統中的垃圾對象很多,復制算法需要復制的存活對象數量并不會太大。因此在真正需要垃圾回收的時刻,復制算法的效率是很高的。又由于對象在垃圾回收過程中統一被復制到新的內存空間中,因此,可確?;厥蘸蟮膬却婵臻g是沒有碎片的。該算法的缺點是將系統內存折半。
Java 的新生代串行垃圾回收器中使用了復制算法的思想。新生代分為 eden 空間、from 空間、to 空間 3 個部分。其中 from 空間和 to 空間可以視為用于復制的兩塊大小相同、地位相等,且可進行角色互換的空間塊。from 和 to 空間也稱為 survivor 空間,即幸存者空間,用于存放未被回收的對象。
在垃圾回收時,eden 空間中的存活對象會被復制到未使用的 survivor 空間中 (假設是 to),正在使用的 survivor 空間 (假設是 from) 中的年輕對象也會被復制到 to 空間中 (大對象,或者老年對象會直接進入老年帶,如果 to 空間已滿,則對象也會直接進入老年代)。此時,eden 空間和 from 空間中的剩余對象就是垃圾對象,可以直接清空,to 空間則存放此次回收后的存活對象。這種改進的復制算法既保證了空間的連續性,又避免了大量的內存空間浪費。
標記-壓縮算法 (Mark-Compact)
復制算法的高效性是建立在存活對象少、垃圾對象多的前提下的。這種情況在年輕代經常發生,但是在老年代更常見的情況是大部分對象都是存活對象。如果依然使用復制算法,由于存活的對象較多,復制的成本也將很高。
標記-壓縮算法是一種老年代的回收算法,它在標記-清除算法的基礎上做了一些優化。也首先需要從根節點開始對所有可達對象做一次標記,但之后,它并不簡單地清理未標記的對象,而是將所有的存活對象壓縮到內存的一端。之后,清理邊界外所有的空間。這種方法既避免了碎片的產生,又不需要兩塊相同的內存空間,因此,其性價比比較高。
增量算法 (Incremental Collecting)
在垃圾回收過程中,應用軟件將處于一種 CPU 消耗很高的狀態。在這種 CPU 消耗很高的狀態下,應用程序所有的線程都會掛起,暫停一切正常的工作,等待垃圾回收的完成。如果垃圾回收時間過長,應用程序會被掛起很久,將嚴重影響用戶體驗或者系統的穩定性。
增量算法的基本思想是,如果一次性將所有的垃圾進行處理,需要造成系統長時間的停頓,那么就可以讓垃圾收集線程和應用程序線程交替執行。每次,垃圾收集線程只收集一小片區域的內存空間,接著切換到應用程序線程。依次反復,直到垃圾收集完成。使用這種方式,由于在垃圾回收過程中,間斷性地還執行了應用程序代碼,所以能減少系統的停頓時間。但是,因為線程切換和上下文轉換的消耗,會使得垃圾回收的總體成本上升,造成系統吞吐量的下降。
分代 (Generational Collecting)
根據垃圾回收對象的特性,不同階段最優的方式是使用合適的算法用于本階段的垃圾回收,分代算法即是基于這種思想,它將內存區間根據對象的特點分成幾塊,根據每塊內存區間的特點,使用不同的回收算法,以提高垃圾回收的效率。以 Hot Spot 虛擬機為例,它將所有的新建對象都放入稱為年輕代的內存區域,年輕代的特點是對象會很快回收,因此,在年輕代就選擇效率較高的復制算法。當一個對象經過幾次回收后依然存活,對象就會被放入稱為老生代的內存空間。在老生代中,幾乎所有的對象都是經過幾次垃圾回收后依然得以幸存的。因此,可以認為這些對象在一段時期內,甚至在應用程序的整個生命周期中,將是常駐內存的。如果依然使用復制算法回收老生代,將需要復制大量對象。再加上老生代的回收性價比也要低于新生代,因此這種做法也是不可取的。根據分代的思想,可以對老年代的回收使用與新生代不同的標記-壓縮算法,以提高垃圾回收效率。
5. 以 分代(Generational Collecting) 算法為例,說明 GC 機制詞匯匯總:
Young generation :新生代 Eden : 伊甸園 (每個新 New 出來的對象最開始存放的位置) Survivor : 幸存區(圖中S0與S1) Tenured / Old Generation :老年代 Permanent Generation :永久代
注意: S0 與 S1 的內存區域是一樣大的
下面講述其 GC 過程:
Step 1:
新創建的對象一般放在新生代的Eden區。
在 Eden 中有 “存活對象” 與 “待回收對象”,當Eden空間被使用完的時候,就會發生新生代GC,也就是Minor GC。
Step 2:
GC 會做如何操作:
把 “存活對象” 復制到S0中。
清空 Eden 區。
將 S0 中的 “存活對象” 年齡(Age)設置為 1。
這樣第一次GC就完成了。
Step 3:
當Eden區再次被使用完的時候,就會再次進行GC操作。
GC 的操作如下:
將 Eden 區和 S0 中的“存活對象” 復制 到S1中。
清空 Eden 和 S0 區。
然后將 Eden 中復制到 S1 中的對象年齡設置為 1,將 S0 中復制到 S1 中的對象年齡加 1。
這樣新生代第二次GC就完成了。
Step 4:
當Eden再一次被使用完的時候,就會發生第三次GC操作了。
之后基本重復上面的思路了,
GC 操作如下:
首先將 Eden 和 S1 中的 “存活對象” 復制到 S0 中。
然后將 Eden 和 S1 進行清空。
最后將 Eden 中復制到 S0 中的對象年齡設置為1,將 S1 中復制到 S0 中的對象年齡加1。
之后就這樣循環了~~~
那 老年代 呢? 何時才會進入 老年代 ?
如果對象在 GC 過程中沒有被回收,那么它的對象年齡(Age)會不斷的增加,對象在Survivor區每熬過一個Minor GC,年齡就增加1歲,當它的年齡到達一定的程度(默認為15歲),就會被移動到老年代,這個年齡閥值可以通過-XX:MaxTenuringThreshold設置。
這些文章或者視頻資料都很不錯,建議有興趣可以看看。
JVM 垃圾回收器工作原理及使用實例介紹
Java GC系列(2):Java垃圾回收是如何工作的?
YouTube 視頻:Garbage collection in Java, with Animation and discussion of G1 GC
Java GC系列(1):Java垃圾回收簡介
JVM內存回收理論與實現
JVM為什么需要GC
這些是整理的筆記,希望對你有幫助。
沒有GC機制的JVM是不能想象的,我們只能通過不斷優化它的使用、不斷調整自己的應用程序,避免出現大量垃圾,而不是一味認為GC造成了應用程序問題。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66933.html
摘要:當一個實例被創建的時候,它最初被存放在堆內存空間的年輕代的區中。老年代或者永久代是堆內存的第二個邏輯部分。在垃圾回收過程中掃描屬于部分的堆內存。一旦實例從堆內存中刪除了,它們原來的位置將空出來給以后分配實例使用。 本文非原創,翻譯自How Java Garbage Collection Works?在Java中為對象分配和釋放內存空間都是由垃圾回收線程自動執行完成的。和C語言不一樣的是...
摘要:垃圾回收算法與垃圾回收器綜述我們常說的垃圾回收算法可以分為兩部分對象的查找算法與真正的回收方法。串行垃圾回收器一次只使用一個線程進行垃圾回收并行垃圾回收器一次將開啟多個線程同時進行垃圾回收。 垃圾回收算法與 JVM 垃圾回收器綜述歸納于筆者的 JVM 內部原理與性能調優系列文章,文中涉及的引用資料參考 Java 學習與實踐資料索引、JVM 資料索引。 showImg(https://s...
摘要:年輕代的目標就是盡可能快速的收集掉那些生命周期短的對象。年老代在年輕代中經歷了次垃圾回收后仍然存活的對象,就會被放到年老代中。什么情況下觸發垃圾回收由于對象進行了分代處理,因此垃圾回收區域時間也不一樣。 [TOC] 與C/C++相比,java語言不需要程序員直接控制內存回收,java程序的內存分配和回收都是由JRE在后臺自動進行,JRE會負責回收那些不再使用的內存,這種機制被稱為垃圾...
摘要:這個算法看似不錯而且簡單,不過存在這一個致命傷當兩個對象互相引用的時候,就永遠不會被回收于是引用計數算法就永遠回收不了這兩個對象,下面介紹另一種算法。 前言 ? 如果要問Java與其他編程語言最大的不同是什么,我第一個想到的一定就是Java所運行的JVM所自帶的自動垃圾回收機制,以下是我學習JVM垃圾回收機制整理的筆記,希望能對讀者有一些幫助。 哪些內存需要回收?what? ? ...
摘要:當兩個對象相互引用時,這兩個對象就不會被回收引用計數算法不被主流虛擬機采用,主要原因是它很難解決對象之間相互循環引用的問題。 垃圾收集器與內存分配策略 詳解 3.1 概述 本文參考的是周志明的 《深入理解Java虛擬機》第三章 ,為了整理思路,簡單記錄一下,方便后期查閱。 3.2 對象已死嗎 在垃圾收集器進行回收前,第一件事就是確定這些對象哪些還存活,哪些已經死去。 3.2.1 引用...
閱讀 2247·2021-11-25 09:43
閱讀 2934·2019-08-30 15:52
閱讀 1885·2019-08-30 15:44
閱讀 975·2019-08-30 10:58
閱讀 752·2019-08-29 18:43
閱讀 3208·2019-08-29 18:36
閱讀 2310·2019-08-29 17:02
閱讀 1447·2019-08-29 17:01