端午佳節(jié)一下子就過(guò)完了软啼,大家是不是還沉迷在假期的歡樂(lè)氣氛中無(wú)法自拔饱普?今天阿Q為大家準(zhǔn)備了上好的“醒酒菜”——JVM
運(yùn)行時(shí)數(shù)據(jù)區(qū)的核心內(nèi)存區(qū)——堆溯饵。
堆的概述
一般來(lái)說(shuō):
- 一個(gè)
Java
程序的運(yùn)行對(duì)應(yīng)一個(gè)進(jìn)程七冲; - 一個(gè)進(jìn)程對(duì)應(yīng)著一個(gè)
JVM
實(shí)例(JVM
的啟動(dòng)由引導(dǎo)類加載器加載啟動(dòng)),同時(shí)也對(duì)應(yīng)著多個(gè)線程殿较; - 一個(gè)
JVM
實(shí)例擁有一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime
類耸峭,為餓漢式單例類); - 一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的堆和方法區(qū)是多線程共享的淋纲,而本地方法棧劳闹、虛擬機(jī)棧、程序計(jì)數(shù)器是線程私有的洽瞬。
堆空間差不多是最大的內(nèi)存空間本涕,也是運(yùn)行時(shí)數(shù)據(jù)區(qū)最重要的內(nèi)存空間。堆可以處于物理上不連續(xù)的內(nèi)存空間片任,但在邏輯上它應(yīng)該被視為連續(xù)的偏友。
在方法結(jié)束后,堆中的對(duì)象不會(huì)馬上被移除对供,僅僅在垃圾收集的時(shí)候才會(huì)被移除位他。堆,是GC
(Garbage Collection
产场,垃圾收集器)執(zhí)行垃圾回收的重點(diǎn)區(qū)域鹅髓。
堆內(nèi)存大小設(shè)置
堆一旦被創(chuàng)建,它的大小也就確定了京景,初始內(nèi)存默認(rèn)為電腦物理內(nèi)存大小的1/64
窿冯,最大內(nèi)存默認(rèn)為電腦物理內(nèi)存的1/4
,但是堆空間的大小是可以調(diào)節(jié)确徙,接下來(lái)我們來(lái)演示一下醒串。
準(zhǔn)備工具
JDK
自帶內(nèi)存分析的工具:在已安裝JDK
的bin
目錄下找到jvisualvm.exe
。打開(kāi)該軟件鄙皇,下載插件Visual GC
芜赌,一定要點(diǎn)擊檢查最新版本,否則會(huì)導(dǎo)致安裝失敗伴逸。
安裝完重啟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設(shè)置
-
-Xms10m
用于表示堆區(qū)的起始內(nèi)存為10m缠沈,等價(jià)于-XX:InitialHeapSize
; -
-Xmx10m
用于表示堆區(qū)的最大內(nèi)存為10m错蝴,等價(jià)于-XX:MaxHeapSize
洲愤; - 其中
-X
是JVM
的運(yùn)行參數(shù),ms
是memory start
?
通常會(huì)將
-Xms
和-Xmx
兩個(gè)參數(shù)配置相同的值顷锰,其目的就是為了能夠在java
垃圾回收機(jī)制清理完堆區(qū)后不需要重新分隔計(jì)算堆區(qū)的大小柬赐,從而提高性能。?
啟動(dòng)程序
啟動(dòng)程序之后去jvisualvm
查看
一旦堆區(qū)中的內(nèi)存大小超過(guò)-Xmx
所指定的最大內(nèi)存時(shí)官紫,將會(huì)拋出OOM
(Out Of MemoryError
)異常躺率。
堆的分代
存儲(chǔ)在JVM
中的java
對(duì)象可以被劃分為兩類:
- 一類是生命周期較短的瞬時(shí)對(duì)象玛界,這類對(duì)象的創(chuàng)建和消亡都非常迅速;
- 另一類是生命周期非常長(zhǎng)悼吱,在某些情況下還能與
JVM
的生命周期保持一致堂飞;
堆區(qū)分代
經(jīng)研究表明70%-99%
的對(duì)象屬于臨時(shí)對(duì)象圃泡,為了提高GC
的性能张足,Hotspot
虛擬機(jī)又將堆區(qū)進(jìn)行了進(jìn)一步劃分蛙讥。
如圖所示,堆區(qū)又分為年輕代(YoungGen
)和老年代(OldGen
)遇西;其中年輕代又分為伊甸園區(qū)(Eden
)和幸存者區(qū)(Survivor
);幸存者區(qū)分為幸存者0區(qū)(Survivor0粱檀,S0
)和幸存者1區(qū)(Survivor1洲敢,S1
),有時(shí)也叫from
區(qū)和to
區(qū)茄蚯。
?
分代完成之后压彭,GC時(shí)主要檢測(cè)新生代
Eden
區(qū)。?
「統(tǒng)一概念:」
新生區(qū)<=>新生代<=>年輕代
養(yǎng)老區(qū)<=>老年區(qū)<=>老年代
幾乎所有的Java
對(duì)象都是在Eden
區(qū)被new
出來(lái)的渗常,有的大對(duì)象在該區(qū)存不下可直接進(jìn)入老年代壮不。絕大部分的Java
對(duì)象都銷毀在新生代了(IBM
公司的專門研究表明,新生代80%的對(duì)象都是“朝生夕死”的)皱碘。
新生代與老年代在堆結(jié)構(gòu)的占比
- 默認(rèn)參數(shù)
-XX:NewRatio=2
询一,表示新生代占1,老年代占2癌椿,新生代占整個(gè)堆的1/3健蕊; - 可以修改
-XX:NewRatio=4
,表示新生代占1踢俄,老年代占4绊诲,新生代占整個(gè)堆的1/5;
?
該參數(shù)在開(kāi)發(fā)中一般不會(huì)調(diào)整,如果生命周期長(zhǎng)的對(duì)象偏多時(shí)可以選擇調(diào)整褪贵。
?
Eden與Survivor在堆結(jié)構(gòu)的占比
在HotSpot
中,Eden
空間和另外兩個(gè)Survivor
空間所占的比例是8:1:1(測(cè)試的時(shí)候是6:1:1)抗俄,開(kāi)發(fā)人員可以通過(guò)選項(xiàng)-XX:SurvivorRatio
調(diào)整空間比例脆丁,如-XX:SurvivorRatio=8
?
可以在
cmd
中通過(guò)jps 查詢進(jìn)程號(hào)-> jinfo -flag NewRatio(SurvivorRatio) + 進(jìn)程號(hào)
查詢配置信息?
-Xmn
設(shè)置新生代最大內(nèi)存大小(默認(rèn)就好)动雹,如果既設(shè)置了該參數(shù)槽卫,又設(shè)置了NewRatio
的值,則以該參數(shù)設(shè)置為準(zhǔn)胰蝠。
查看設(shè)置的參數(shù)
以上邊的代碼為例:設(shè)置啟動(dòng)參數(shù)-XX:+PrintGCDetails
歼培;可在cmd窗口中輸入jps
查詢進(jìn)程號(hào)震蒋,然后通過(guò)jstat -gc 進(jìn)程id
指令查看進(jìn)程的內(nèi)存使用情況。
圖解對(duì)象分配過(guò)程
對(duì)象分配過(guò)程
- new的對(duì)象先放伊甸園區(qū)躲庄,此區(qū)有大小限制查剖;
- 當(dāng)伊甸園的空間填滿時(shí),程序繼續(xù)創(chuàng)建對(duì)象噪窘,
JVM
的垃圾回收器將對(duì)伊甸園區(qū)進(jìn)行垃圾回收(Minor GC
笋庄,也叫YGC
):將伊甸園區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷毀,將未被銷毀的對(duì)象移動(dòng)到幸存者0區(qū)并分配age
倔监; - 然后再加載新的對(duì)象放到伊甸園區(qū)直砂;
- 如果再次觸發(fā)垃圾回收,將此次未被銷毀的對(duì)象和上一次放在幸存者0區(qū)且此次也未被銷毀的對(duì)象一齊移動(dòng)到幸存者一區(qū)浩习,此時(shí)新對(duì)象的
age
為1静暂,上次的對(duì)象的age
加1變?yōu)?; - 如果再次經(jīng)歷垃圾回收谱秽,此時(shí)會(huì)重新放回幸存者0區(qū)洽蛀,接著再去幸存者1區(qū),
age
也隨之增加弯院; - 默認(rèn)當(dāng)
age
為15時(shí)辱士,未被回收的對(duì)象將移動(dòng)到老年區(qū)√可以通過(guò)設(shè)置參數(shù)來(lái)更改默認(rèn)配置:-XX:MaxTenuringThreshold=<N>
颂碘;該過(guò)程稱為晉升(promotion
); - 在養(yǎng)老區(qū)椅挣,相對(duì)悠閑头岔,當(dāng)老年區(qū)內(nèi)存不足時(shí),再次觸發(fā)GC(
Major GC
)鼠证,進(jìn)行養(yǎng)老區(qū)的內(nèi)存清理峡竣; - 若養(yǎng)老區(qū)執(zhí)行了
Major GC
之后發(fā)現(xiàn)依然無(wú)法進(jìn)行對(duì)象的保存,就會(huì)產(chǎn)生OOM
異常量九。
?
S0适掰,S1滿時(shí)不會(huì)觸發(fā)
YGC
,但是YGC
會(huì)回收S0荠列,S1的對(duì)象类浪。?
「總結(jié)」
- 針對(duì)幸存者s0,s1區(qū):復(fù)制之后有交換肌似,誰(shuí)空誰(shuí)是to费就;
- 關(guān)于垃圾回收:頻繁在新生區(qū)收集,很少在養(yǎng)老區(qū)收集川队,幾乎不再永久區(qū)/元空間收集力细。
對(duì)象特殊情況分配過(guò)程
- 新對(duì)象申請(qǐng)內(nèi)存睬澡,如果
Eden
放的下,則直接存入Eden
眠蚂;如果存不下則進(jìn)行YGC
煞聪; -
YGC
之后如果能存下則放入Eden
,如果還存不下(為超大對(duì)象)河狐,則嘗試存入Old
區(qū)米绕; - 如果
Old
區(qū)可以存放,則存入馋艺;如果不能存入栅干,則進(jìn)行Full GC
; -
Full GC
之后如果可以存入Old
區(qū)捐祠,則存入碱鳞;如果內(nèi)存空間還不夠,則OOM
踱蛀; - 圖右側(cè)為
YGC
的流程圖:當(dāng)YGC
之后未銷毀的對(duì)象放入幸存者區(qū)窿给,此時(shí)如果幸存者區(qū)的空間可以裝下該對(duì)象,則存入幸存者區(qū)率拒,否則崩泡,直接存入老年代; - 當(dāng)在幸存者區(qū)的對(duì)象超過(guò)閾值時(shí)猬膨,可以晉升為老年代角撞,未達(dá)到閾值的依舊在幸存者區(qū)復(fù)制交換。
內(nèi)存分配策略
針對(duì)不同年齡段的對(duì)象分配原則如下:
- 優(yōu)先分配到
Eden
勃痴; - 大對(duì)象直接分配到老年代:盡量避免程序中出現(xiàn)過(guò)多的大對(duì)象谒所;
- 長(zhǎng)期存活的對(duì)象分配到老年代;
- 動(dòng)態(tài)對(duì)象年齡判斷:如果
Survivor
區(qū)中相同年齡的所有對(duì)象大小的總和大于Survivor
空間的一半沛申,年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入到老年代劣领。無(wú)需等到MaxTenuringThreshold
中要求的年齡;
數(shù)值變小原理
代碼樣例铁材,設(shè)置參數(shù):-Xms600m尖淘,-Xmx600m
public class HeapSpaceInitial {
public static void main(String[] args) {
//返回Java虛擬機(jī)中的堆內(nèi)存總量
long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
//返回Java虛擬機(jī)試圖使用的最大堆內(nèi)存量
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();
}
}
}
//執(zhí)行結(jié)果
-Xms : 575M
-Xmx : 575M
明明設(shè)置的600M,怎么變成575M了呢著觉?這是因?yàn)樵诙褍?nèi)存存取數(shù)據(jù)時(shí)村生,新生代里邊只有伊甸園和幸存者1區(qū)或者是幸存者2區(qū)存儲(chǔ)對(duì)象,所以會(huì)少一個(gè)幸存者區(qū)的內(nèi)存空間固惯。
GC
JVM
進(jìn)行GC
時(shí),并非每次都對(duì)新生代缴守、老年代葬毫、方法區(qū)(永久代镇辉、元空間)這三個(gè)區(qū)域一起回收,大部分回收是指新生代贴捡。
針對(duì)HotSpot VM
的實(shí)現(xiàn)忽肛,它里面的GC
按照回收區(qū)域又分為兩大種類型:一種是部分收集(Partial GC
),一種是整堆收集(Full GC
)
Partial GC
部分收集:不是完整收集整個(gè)Java
堆的垃圾收集烂斋。其中又分為:
- 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集屹逛;
- 老年代收集(Major GC/Old GC):只是老年代的垃圾收集;
- 混合收集(Mixed GC):收集整個(gè)新生代以及部分老年代的垃圾收集汛骂,只有
G1 GC
(按照region
劃分新生代和老年代的數(shù)據(jù))會(huì)有這種行為罕模。
目前,只有CMS GC
會(huì)有單獨(dú)收集老年代的行為帘瞭;很多時(shí)候Major GC
會(huì)和Full GC
混淆使用淑掌,需要具體分辨是老年代回收還是整堆回收。
Full GC
整堆收集(Full GC
):整個(gè)java
堆和方法區(qū)的垃圾收集蝶念。
觸發(fā)機(jī)制
年輕代GC(Minor GC)觸發(fā)機(jī)制
- 當(dāng)年輕代空間不足時(shí)抛腕,就會(huì)觸發(fā)
Minor GC
,這里的年輕代滿指的是Eden
代滿媒殉,Survivor
滿不會(huì)引發(fā)GC
担敌。(每次Minor GC
會(huì)清理年輕代的內(nèi)存,Survivor
是被動(dòng)GC
廷蓉,不會(huì)主動(dòng)GC
) - 因?yàn)?code>Java對(duì)象大多都具備“朝生夕滅”的特性全封,所以
Minor GC
非常頻繁,一般回收速度也比較快苦酱。 -
Minor GC
會(huì)引發(fā)STW
(Stop The World
)售貌,暫停其他用戶的線程,等垃圾回收結(jié)束疫萤,用戶線程才恢復(fù)運(yùn)行颂跨。
老年代GC(Major GC/Full GC)觸發(fā)機(jī)制
- 指發(fā)生在老年代的
GC
,對(duì)象從老年代消失時(shí)扯饶,Major GC
或者Full GC
發(fā)生了恒削; - 出現(xiàn)了
Major GC
,經(jīng)常會(huì)伴隨至少一次的Minor GC
(不是絕對(duì)的尾序,在Parallel Scavenge
收集器的收集策略里就有直接進(jìn)行Major GC
的策略選擇過(guò)程)钓丰,也就是老年代空間不足時(shí),會(huì)先嘗試觸發(fā)Minor GC
每币。如果之后空間還不足携丁,則觸發(fā)Major GC
; -
Major GC
速度一般會(huì)比Minor GC
慢10倍以上,STW
時(shí)間更長(zhǎng)梦鉴; - 如果
Major GC
后李茫,內(nèi)存還不足,就報(bào)OOM
了肥橙。
Full GC觸發(fā)機(jī)制
觸發(fā)Full GC執(zhí)行的情況有以下五種:
- 調(diào)用
System.gc()
時(shí)魄宏,系統(tǒng)建議執(zhí)行Full GC
,但是不必然執(zhí)行存筏; - 老年代空間不足宠互;
- 方法區(qū)空間不足;
- 通過(guò)
Minor GC
后進(jìn)入老年代的平均大小小于老年代的可用內(nèi)存椭坚; - 由
Eden
區(qū)予跌,Survivor S0
(from
)區(qū)向S1
(to
)區(qū)復(fù)制時(shí),對(duì)象大小大于To Space
可用內(nèi)存藕溅,則把該對(duì)象轉(zhuǎn)存到老年代匕得,且老年代的可用內(nèi)存小于該對(duì)象大小。
?
Full GC
是開(kāi)發(fā)或調(diào)優(yōu)中盡量要避免的巾表,這樣暫停時(shí)間會(huì)短一些汁掠。?
以上就是今天的所有內(nèi)容了,如果你有不同的意見(jiàn)或者更好的idea
集币,歡迎聯(lián)系阿Q:qingqing-4132
考阱,阿Q期待你的到來(lái)!
后臺(tái)留言領(lǐng)取java干貨資料:學(xué)習(xí)筆記與大廠面試題