㈠ java的gc為什麼要分代
假如哈,現在的計算機能做的1ms掃描完所有live object,10ms完成live set的整理(compaction),大多數java應用都會覺得「這沒毛病了」,那麼,現在Hotspot JVM設計的那幾套GC演算法組合確實就沒意義了。下面,再繼續談一哈GC的哲學。類似分布式系統的CAP theorem,GC演算法設計也是有這個3取2的三角組合的:即延時(latency)、吞吐(throughput)和內存消耗(footprint)。基本的設計原理就是footprint為有限值的條件下,我們再在latency和throughput上挑一個優化,比如Hotspot JVM實現中,CMS演算法主攻latency,Parallel GC 主攻throughput,G1 GC較關注latency同時兼顧一點throughput。來來來,我們開個腦洞:我們能不能放棄或減弱「footprint為有限值」這個條件。嗯~比如,一個應用1小時使用100G memory(暫時不管這100G會產生多少垃圾),伺服器24小時會重啟一次,那麼,每次重啟前java應用需要使用的內存會達到2,400G。也就是說,在這個case中,java能使用的內存如果能大於2,400G,我們根本就是不需要任何GC演算法,not to mention 什麼分代了; 「java的gc為什麼要分代」的哲學又是啥。我認為,是熵增原理 和 80/20法則。
㈡ Java垃圾回收:GC在什麼時候對什麼做了什麼
1、首先,GC又分為minor GC 和 Full GC(major GC)。Java堆內存分為新生代和老年代,新生代中又分為1個eden區和兩個Survior區域。
2、一般情況下,新創建的對象都會被分配到eden區,這些對象經過一個minor gc後仍然存活將會被移動到Survior區域中,對象在Survior中沒熬過一個Minor GC,年齡就會增加一歲,當他的年齡到達一定程度時,就會被移動到老年代中。
3、當eden區滿時,還存活的對象將被復制到survior區,當一個survior區滿時,此區域的存活對象將被復制到另外一個survior區,當另外一個也滿了的時候,從前一個Survior區復制過來的並且此時還存活的對象,將可能被復制到老年代。因為年輕代中的對象基本都是朝生夕死(80%以上),所以年輕代的垃圾回收演算法使用的是復制演算法,復制演算法的基本思想是將內存分為兩塊,每次只有其中一塊,當這一塊內存使用完,就將還活著的對象復制到另一塊上面。復制演算法不會產生內存碎片。
4、在GC開始的時候,對象只會存在於eden區,和名為「From」的Survior區,Survior區「to」是空的。緊接著GCeden區中所有存活的對象都會被復制到「To」,而在from區中,仍存活的對象會根據他們的年齡值來決定去向,年齡到達一定只的對象會被復制到老年代,沒有到達的對象會被復制到to survior中,經過這次gc後,eden區和fromsurvior區已經被清空。這個時候,from和to會交換他們的角色,也就是新的to就是上次GC前的fromMinor GC:從年輕代回收內存。
5、當jvm無法為一個新的對象分配空間時會觸發Minor GC,比如當Eden區滿了。當內存池被填滿的時候,其中的內容全部會被復制,指針會從0開始跟蹤空閑內存。Eden和Survior區不存在內存碎片寫指針總是停留在所使用內存池的頂部。執行minor操作時不會影響到永久代,從永久帶到年輕代的引用被當成GC roots,從年輕代到永久代的引用在標記階段被直接忽略掉(永久代用來存放java的類信息)。如果eden區域中大部分對象被認為是垃圾,永遠也不會復制到Survior區域或者老年代空間。如果正好相反,eden區域大部分新生對象不符合GC條件,Minor GC執行時暫停的線程時間將會長很多。Minor may call "stop the world"。
㈢ Java垃圾回收:GC在什麼時候對什麼做了什麼
GC在什麼時候對什麼做了什麼?
要回答這個問題,先了解下GC的發展史、jvm運行時數據區的劃分、jvm內存分配策略、jvm垃圾收集演算法等知識。
先說下jvm運行時數據的劃分,粗暴的分可以分為堆區(Heap)和棧區(Stack),但jvm的分法實際上比這復雜得多,大概分為下面幾塊:
1、程序計數器(Program Conuter Register)
程序計數器是一塊較小的內存空間,它是當前線程執行位元組碼的行號指示器,位元組碼解釋工作器就是通過改變這個計數器的值來選取下一條需要執行的指令。它是線程私有的內存,也是唯一一個沒有OOM異常的區域。
2、Java虛擬機棧區(Java Virtual Machine Stacks)
也就是通常所說的棧區,它描述的是Java方法執行的內存模型,每個方法被執行的時候都創建一個棧幀(Stack Frame),用於存儲局部變數表、操作數棧、動態鏈接、方法出口等。每個方法被調用到完成,相當於一個棧幀在虛擬機棧中從入棧到出棧的過程。此區域也是線程私有的內存,可能拋出兩種異常:如果線程請求的棧深度大於虛擬機允許的深度將拋出StackOverflowError;如果虛擬機棧可以動態的擴展,擴展到無法動態的申請到足夠的內存時會拋出OOM異常。
3、本地方法棧(Native Method Stacks)
本地方法棧與虛擬機棧發揮的作用非常相似,區別就是虛擬機棧為虛擬機執行Java方法,本地方法棧則是為虛擬機使用到的Native方法服務。
4、堆區(Heap)
所有對象實例和數組都在堆區上分配,堆區是GC主要管理的區域。堆區還可以細分為新生代、老年代,新生代還分為一個Eden區和兩個Survivor區。此塊內存為所有線程共享區域,當堆中沒有足夠內存完成實例分配時會拋出OOM異常。
5、方法區(Method Area)
方法區也是所有線程共享區,用於存儲已被虛擬機載入的類信息、常量、靜態變數、即時編譯後的代碼等數據。GC在這個區域很少出現,這個區域內存回收的目標主要是對常量池的回收和類型的卸載,回收的內存比較少,所以也有稱這個區域為永久代(Permanent Generation)的。當方法區無法滿足內存分配時拋出OOM異常。
6、運行時常量池(Runtime Constant Pool)
運行時常量池是方法區的一部分,用於存放編譯期生成的各種字面量和符號引用。
垃圾收集(Garbage Collection)並不是Java獨有的,最早是出現在Lisp語言中,它做的事就是自動管理內存,也就是下面三個問題:
1、什麼時候回收
2、哪些內存需要回收
3、如何回收
1、什麼時候回收?
上面說到GC經常發生的區域是堆區,堆區還可以細分為新生代、老年代,新生代還分為一個Eden區和兩個Survivor區。
1.1 對象優先在Eden中分配,當Eden中沒有足夠空間時,虛擬機將發生一次Minor GC,因為Java大多數對象都是朝生夕滅,所以Minor GC非常頻繁,而且速度也很快;
1.2 Full GC,發生在老年代的GC,當老年代沒有足夠的空間時即發生Full GC,發生Full GC一般都會有一次Minor GC。大對象直接進入老年代,如很長的字元串數組,虛擬機提供一個-XX:PretenureSizeThreadhold參數,令大於這個參數值的對象直接在老年代中分配,避免在Eden區和兩個Survivor區發生大量的內存拷貝;
1.3 發生Minor GC時,虛擬機會檢測之前每次晉升到老年代的平均大小是否大於老年代的剩餘空間大小,如果大於,則進行一次Full GC,如果小於,則查看HandlePromotionFailure設置是否允許擔保失敗,如果允許,那隻會進行一次Minor GC,如果不允許,則改為進行一次Full GC。
2、哪些內存需要回收
jvm對不可用的對象進行回收,哪些對象是可用的,哪些是不可用的?Java並不是採用引用計數演算法來判定對象是否可用,而是採用根搜索演算法(GC Root Tracing),當一個對象到GC Roots沒有任何引用相連接,用圖論的來說就是從GC Roots到這個對象不可達,則證明此對象是不可用的,說明此對象可以被GC。對於這些不可達對象,也不是一下子就被GC,而是至少要經歷兩次標記過程:如果對象在進行根搜索演算法後發現沒有與GC Roots相連接的引用鏈,那它將會第一次標記並且進行一次篩選,篩選條件是此對象有沒有必要執行finalize()方法,當對象沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機調用執行過一次,這兩種情況都被視為沒有必要執行finalize()方法,對於沒有必要執行finalize()方法的將會被GC,對於有必要有必要執行的,對象在finalize()方法中可能會自救,也就是重新與引用鏈上的任何一個對象建立關聯即可。
3、如何回收
選擇不同的垃圾收集器,所使用的收集演算法也不同。
在新生代中,每次垃圾收集都發現有大批對象死去,只有少量存活,則使用復制演算法,新生代內存被分為一個較大的Eden區和兩個較小的Survivor區,每次只使用Eden區和一個Survivor區,當回收時將Eden區和Survivor還存活著的對象一次性的拷貝到另一個Survivor區上,最後清理掉Eden區和剛才使用過的Survivor區,Eden和Survivor的默認比例是8:1,可以使用-XX:SurvivorRatio來設置該比例。
而老年代中對象存活率高,沒有額外的空間對它進行分配擔保,必須使用「標記-清理」或「標記-整理」演算法。