JVM簡述下包括什么?
一,JVM內存結構:在JVM內存結構中,大致(為什么是大致?因為虛擬機的種類繁多,有些虛擬機把棧概念合二為一,還有其他自由的實現)會分為如下的結構:
包括本地方法棧,VM棧,程序計數器,方法區,java堆,下面逐一來看下他們的功能:
①本地方法棧:放著大量虛擬機可直接調用的native方法,比如CAS模型中大量使用的Unsafe包中的方法,基本都是native方法,這些方法很多并不是用java實現的,而是C,C++等;但是可供java直接調用;
②,vm棧:存放線程執行方法時產生的棧幀,通常一個線程會有一個棧幀鏈,如下圖所示,一個線程正在執行的棧幀只會是一個當前棧幀,棧幀中包含的數據結構包括:局部變量表(方法中的局部變量)、操作數棧(運算過程中的中間存儲媒介)、動態鏈接、方法返回地址和一些額外的附加信息,如下圖:
③,程序計數器:每個線程記錄指令執行到的位置,通過程序計數器控制諸如循環,異常處理等的下一條指令;
④,方法區:存放已經被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯后的代碼等數據,還有運行時常量,通常稱為永久代,通常情況不會進行GC;
⑤,java堆:絕大多數實例對象都在此存放;
換個圖來看java內存模型可知:方法區和堆是線程共享的,其他的區域是線程私有的;
二,JVM GC:
1),對象是否能回收的判斷:
(1),不可達性對象可以回收;
(2),對于用可達性分析法搜索不到的對象,GC并不一定會回收該對象。要完全回收一個對象,至少需要經過兩次標記的過程:
第一次標記:對于一個沒有其他引用的對象,篩選該對象是否有必要執行finalize()方法,如果沒有執行必要,則意味可直接回收。(篩選依據:是否復寫或執行過finalize()方法;因為finalize方法只能被執行一次)。
第二次標記:如果被篩選判定位有必要執行,則會放入FQueue隊列,并自動創建一個低優先級的finalize線程來執行釋放操作。如果在一個對象釋放前被其他對象引用,則該對象會被移除FQueue隊列。
根搜索算法:JVM選定諸如方法區的靜態常量,本地方法中的對象等作為GC roots(對象可達樹的根節點),將創建的所有的對象引用掛在樹上,當對象引用從樹上解掛時(沒有對象再引用這個對象時),則這個對象處于不可達狀態,也即是可回收狀態;如下圖:
2)JVM內存分區和GC算法
內存被切分為三塊:新生代(剛new出來的對象),老年代(從新生代GC過來的對象或者剛new出來的大對象(直接超過了新生代的空閑內存)),永久代(方法區數據)
新生代又被分為一塊Eden區和兩塊Survivor區;
新生代GC算法:復制算法采用的方式為從根集合進行掃描,將存活的對象移動到一塊空閑的區域
標記-清除:該算法采用的方式是從跟集合開始掃描,對存活的對象進行標記,標記完畢后,再掃描整個空間中未被標記的對象,并進行清除。標記和清除的過程如下:
上圖中藍色部分是有被引用的對象,褐色部分是沒有被引用的對象。在Marking階段,需要進行全盤掃描,這個過程是比較耗時的。
清除階段清理的是沒有被引用的對象,存活的對象被保留。
標記-清除動作不需要移動對象,且僅對不存活的對象進行清理,在空間中存活對象較多的時候,效率較高,但由于只是清除,沒有重新整理,因此會造成內存碎片。
標記-壓縮:該算法與標記-清除算法類似,都是先對存活的對象進行標記,但是在清除后會把活的對象向左端空閑空間移動,然后再更新其引用對象的指針,如下圖所示
由于進行了移動規整動作,該算法避免了標記-清除的碎片問題,但由于需要進行移動,因此成本也增加了。(該算法適用于舊生代)
3),虛擬機中GC的過程:
1,在初始階段,新創建的對象被分配到Eden區,survivor的兩塊空間都為空。
2,當Eden區滿了的時候,minor garbage 被觸發 。
3,經過掃描與標記,存活的對象被復制到S0,不存活的對象被回收
4,在下一次的Minor GC中,Eden區的情況和上面一致,沒有引用的對象被回收,存活的對象被復制到survivor區。然而在survivor區,S0的所有的數據都被復制到S1,需要注意的是,在上次minor GC過程中移動到S0中的兩個對象在復制到S1后其年齡要加1。此時Eden區S0區被清空,所有存活的數據都復制到了S1區,并且S1區存在著年齡不一樣的對象,過程如下圖所示:
5,再下一次MinorGC則重復這個過程,這一次survivor的兩個區對換,存活的對象被復制到S0,存活的對象年齡加1,Eden區和另一個survivor區被清空。
6,再經過幾次Minor GC之后,當存活對象的年齡達到一個閾值之后(可通過參數配置,默認是8),就會被從年輕代Promotion到老年代。
7,隨著MinorGC一次又一次的進行,不斷會有新的對象被promote到老年代。
8,上面基本上覆蓋了整個年輕代所有的回收過程。最終,MajorGC將會在老年代發生,老年代的空間將會被清除和壓縮。
4),Minor GC,Major GC,Full GC觸發條件
Minor GC:當年輕代中的Eden區滿時,觸發;
Major GC:清理老年代,通常由Minor GC觸發;
Full GC:
(1)調用System.gc時,建議執行full GC,但是不一定執行;
(2)老年代空間不足,
(3)方法區空間不足,
(4)通過Minor GC進入老年代的平均大小大于老年代的可用內存;
(5)Minor GC觸發Full GC:新生代的eden區和suvivor(使用中)向survivor(暫未使用)復制對象的時候,大于survivor(暫未使用)的內存,隨即把對象轉存到老年代,但同樣大于老年代的可用永存
5),JVM參數與調優:
JVM參數:
-Xmx:最大允許分配堆內存;
-Xms:初始分配的堆內存; 通常與Xmx一樣,避免每次GC后重新分配內存
CMSFullGCsBeforeCompaction=5:會每隔5次真正的full GC做一次壓縮
-XX:CMSInitiatingOccupancyFraction=70 是指設定CMS在對內存占用率達到70%的時候開始GC(因為CMS會有浮動垃圾,所以一般都較早啟動GC);
-XX:+CMSParallelRemarkEnabled 減少第二次暫停的時間,開啟并行的remark;
-XX:+DisableExplicitGC 禁止代碼中顯式的調用GC,
-XX:+DoEscapeAnalysis 打開逃逸(例如被靜態變量引用等導致無法回收)分析
XX:+UseCMSCompactAtFullCollection 開啟碎片合并
XX:+UseConcMarkSweepG 使用CMS收集器
-XX:+UseParNewGC 年輕代為多線程收集。
還有更多的JAVA干貨技術分享,敬請關注。。。