端午佳節一下子就過完了,大家是不是還沉迷在假期的歡樂氣氛中無法自拔?今天阿Q為大家準備了上好的“醒酒菜”——JVM運行時資料區的核心記憶體區——堆,
堆的概述
一般來說:
- 一個
Java程式的運行對應一個行程; - 一個行程對應著一個
JVM實體(JVM的啟動由引導類加載器加載啟動),同時也對應著多個執行緒; - 一個
JVM實體擁有一個運行時資料區(Runtime類,為餓漢式單例類); - 一個運行時資料區中的堆和方法區是多執行緒共享的,而本地方法堆疊、虛擬機堆疊、程式計數器是執行緒私有的,
堆空間差不多是最大的記憶體空間,也是運行時資料區最重要的記憶體空間,堆可以處于物理上不連續的記憶體空間,但在邏輯上它應該被視為連續的,
在方法結束后,堆中的物件不會馬上被移除,僅僅在垃圾收集的時候才會被移除,堆,是GC(Garbage Collection,垃圾收集器)執行垃圾回收的重點區域,
堆記憶體大小設定
堆一旦被創建,它的大小也就確定了,初始記憶體默認為電腦物理記憶體大小的1/64,最大記憶體默認為電腦物理記憶體的1/4,但是堆空間的大小是可以調節,接下來我們來演示一下,
準備工具
JDK自帶記憶體分析的工具:在已安裝JDK的bin目錄下找到jvisualvm.exe,打開該軟體,下載插件Visual GC,一定要點擊檢查最新版本,否則會導致安裝失敗,

安裝完重啟jvisualvm

代碼樣例
public class HeapDemo {
public static void main(String[] args) {
System.out.println("start...");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
IDEA設定


-Xms10m用于表示堆區的起始記憶體為10m,等價于-XX:InitialHeapSize;-Xmx10m用于表示堆區的最大記憶體為10m,等價于-XX:MaxHeapSize;- 其中
-X是JVM的運行引數,ms是memory start
通常會將
-Xms和-Xmx兩個引數配置相同的值,其目的就是為了能夠在java垃圾回識訓制清理完堆區后不需要重新分隔計算堆區的大小,從而提高性能,
啟動程式
啟動程式之后去jvisualvm查看

一旦堆區中的記憶體大小超過-Xmx所指定的最大記憶體時,將會拋出OOM(Out Of MemoryError)例外,
堆的分代
存盤在JVM中的java物件可以被劃分為兩類:
- 一類是生命周期較短的瞬時物件,這類物件的創建和消亡都非常迅速;
- 另一類是生命周期非常長,在某些情況下還能與
JVM的生命周期保持一致;
堆區分代
經研究表明70%-99%的物件屬于臨時物件,為了提高GC的性能,Hotspot虛擬機又將堆區進行了進一步劃分,

如圖所示,堆區又分為年輕代(YoungGen)和老年代(OldGen);其中年輕代又分為伊甸園區(Eden)和幸存者區(Survivor);幸存者區分為幸存者0區(Survivor0,S0)和幸存者1區(Survivor1,S1),有時也叫from區和to區,
分代完成之后,GC時主要檢測新生代
Eden區,
統一概念:
新生區<=>新生代<=>年輕代
養老區<=>老年區<=>老年代
幾乎所有的Java物件都是在Eden區被new出來的,有的大物件在該區存不下可直接進入老年代,絕大部分的Java物件都銷毀在新生代了(IBM公司的專門研究表明,新生代80%的物件都是“朝生夕死”的),
新生代與老年代在堆結構的占比
- 默認引數
-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整個堆的1/3; - 可以修改
-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整個堆的1/5;
該引數在開發中一般不會調整,如果生命周期長的物件偏多時可以選擇調整,
Eden與Survivor在堆結構的占比
在HotSpot中,Eden空間和另外兩個Survivor空間所占的比例是8:1:1(測驗的時候是6:1:1),開發人員可以通過選項-XX:SurvivorRatio調整空間比例,如-XX:SurvivorRatio=8
可以在
cmd中通過jps 查詢行程號-> jinfo -flag NewRatio(SurvivorRatio) + 行程號查詢配置資訊
-Xmn設定新生代最大記憶體大小(默認就好),如果既設定了該引數,又設定了NewRatio的值,則以該引數設定為準,
查看設定的引數
以上邊的代碼為例:設定啟動引數-XX:+PrintGCDetails;可在cmd視窗中輸入jps查詢行程號,然后通過jstat -gc 行程id指令查看行程的記憶體使用情況,

圖解物件分配程序
物件分配程序

- new的物件先放伊甸園區,此區有大小限制;
- 當伊甸園的空間填滿時,程式繼續創建物件,
JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC,也叫YGC):將伊甸園區中的不再被其他物件所參考的物件進行銷毀,將未被銷毀的物件移動到幸存者0區并分配age; - 然后再加載新的物件放到伊甸園區;
- 如果再次觸發垃圾回收,將此次未被銷毀的物件和上一次放在幸存者0區且此次也未被銷毀的物件一齊移動到幸存者一區,此時新物件的
age為1,上次的物件的age加1變為2; - 如果再次經歷垃圾回收,此時會重新放回幸存者0區,接著再去幸存者1區,
age也隨之增加; - 默認當
age為15時,未被回收的物件將移動到老年區,可以通過設定引數來更改默認配置:-XX:MaxTenuringThreshold=<n>;該程序稱為晉升(promotion); - 在養老區,相對悠閑,當老年區記憶體不足時,再次觸發GC(
Major GC),進行養老區的記憶體清理; - 若養老區執行了
Major GC之后發現依然無法進行物件的保存,就會產生OOM例外,
S0,S1滿時不會觸發
YGC,但是YGC會回收S0,S1的物件,
總結
- 針對幸存者s0,s1區:復制之后有交換,誰空誰是to;
- 關于垃圾回收:頻繁在新生區收集,很少在養老區收集,幾乎不再永久區/元空間收集,
物件特殊情況分配程序

- 新物件申請記憶體,如果
Eden放的下,則直接存入Eden;如果存不下則進行YGC; YGC之后如果能存下則放入Eden,如果還存不下(為超大物件),則嘗試存入Old區;- 如果
Old區可以存放,則存入;如果不能存入,則進行Full GC; Full GC之后如果可以存入Old區,則存入;如果記憶體空間還不夠,則OOM;- 圖右側為
YGC的流程圖:當YGC之后未銷毀的物件放入幸存者區,此時如果幸存者區的空間可以裝下該物件,則存入幸存者區,否則,直接存入老年代; - 當在幸存者區的物件超過閾值時,可以晉升為老年代,未達到閾值的依舊在幸存者區復制交換,
記憶體分配策略
針對不同年齡段的物件分配原則如下:
- 優先分配到
Eden; - 大物件直接分配到老年代:盡量避免程式中出現過多的大物件;
- 長期存活的物件分配到老年代;
- 動態物件年齡判斷:如果
Survivor區中相同年齡的所有物件大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的物件可以直接進入到老年代,無需等到MaxTenuringThreshold中要求的年齡;
數值變小原理
代碼樣例,設定引數:-Xms600m,-Xmx600m
public class HeapSpaceInitial {
public static void main(String[] args) {
//回傳Java虛擬機中的堆記憶體總量
long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
//回傳Java虛擬機試圖使用的最大堆記憶體量
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
System.out.println("-Xms : " + initialMemory + "M");
System.out.println("-Xmx : " + maxMemory + "M");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//執行結果
-Xms : 575M
-Xmx : 575M
明明設定的600M,怎么變成575M了呢?這是因為在堆記憶體存取資料時,新生代里邊只有伊甸園和幸存者1區或者是幸存者2區存盤物件,所以會少一個幸存者區的記憶體空間,
GC
JVM進行GC時,并非每次都對新生代、老年代、方法區(永久代、元空間)這三個區域一起回收,大部分回收是指新生代,
針對HotSpot VM的實作,它里面的GC按斬訓收區域又分為兩大種型別:一種是部分收集(Partial GC),一種是整堆收集(Full GC)
Partial GC
部分收集:不是完整收集整個Java堆的垃圾收集,其中又分為:
- 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集;
- 老年代收集(Major GC/Old GC):只是老年代的垃圾收集;
- 混合收集(Mixed GC):收集整個新生代以及部分老年代的垃圾收集,只有
G1 GC(按照region劃分新生代和老年代的資料)會有這種行為,
目前,只有CMS GC會有單獨收集老年代的行為;很多時候Major GC會和Full GC 混淆使用,需要具體分辨是老年代回識訓是整堆回收,
Full GC
整堆收集(Full GC):整個java堆和方法區的垃圾收集,
觸發機制
年輕代GC(Minor GC)觸發機制
- 當年輕代空間不足時,就會觸發
Minor GC,這里的年輕代滿指的是Eden代滿,Survivor滿不會引發GC,(每次Minor GC會清理年輕代的記憶體,Survivor是被動GC,不會主動GC) - 因為
Java物件大多都具備“朝生夕滅”的特性,所以Minor GC非常頻繁,一般回收速度也比較快, Minor GC會引發STW(Stop The World),暫停其他用戶的執行緒,等垃圾回收結束,用戶執行緒才恢復運行,
老年代GC(Major GC/Full GC)觸發機制
- 指發生在老年代的
GC,物件從老年代消失時,Major GC或者Full GC發生了; - 出現了
Major GC,經常會伴隨至少一次的Minor GC(不是絕對的,在Parallel Scavenge收集器的收集策略里就有直接進行Major GC的策略選擇程序),也就是老年代空間不足時,會先嘗試觸發Minor GC,如果之后空間還不足,則觸發Major GC; Major GC速度一般會比Minor GC慢10倍以上,STW時間更長;- 如果
Major GC后,記憶體還不足,就報OOM了,
Full GC觸發機制
觸發Full GC執行的情況有以下五種:
- 呼叫
System.gc()時,系統建議執行Full GC,但是不必然執行; - 老年代空間不足;
- 方法區空間不足;
- 通過
Minor GC后進入老年代的平均大小小于老年代的可用記憶體; - 由
Eden區,Survivor S0(from)區向S1(to)區復制時,物件大小大于To Space可用記憶體,則把該物件轉存到老年代,且老年代的可用記憶體小于該物件大小,
Full GC是開發或調優中盡量要避免的,這樣暫停時間會短一些,
以上就是今天的所有內容了,如果你有不同的意見或者更好的idea,歡迎聯系阿Q:qingqing-4132,阿Q期待你的到來!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/287523.html
標籤:Java
