一個(gè)線上服務(wù)內(nèi)存占用帶來(lái)的問(wèn)題
在Oracle官網(wǎng)給出的JVM調(diào)優(yōu)文檔中關(guān)于堆的調(diào)整技巧有以下三個(gè)方面:
1.應(yīng)將堆大小設(shè)置為不超過(guò)最大可用物理RAM量丈氓。如果超過(guò)此值竟闪,操作系統(tǒng)將開(kāi)始分頁(yè)腾夯,性能會(huì)顯著下降揭璃。VM總是使用比堆大小更多的內(nèi)存狮斗。除了堆大小設(shè)置之外推汽,還分配內(nèi)部VM功能所需的內(nèi)存补疑,VM外部的本地類庫(kù)和永久區(qū)(僅適用于Sun虛擬機(jī):存儲(chǔ)類和方法所需的內(nèi)存)。
2.使用分代垃圾收集方案時(shí)民泵,年輕代大小不應(yīng)超過(guò)Java堆總大小的一半癣丧。通常,堆大小的25%到40%就足夠了栈妆。
3.在生產(chǎn)環(huán)境中胁编,將最小堆大小和最大堆大小設(shè)置為相同的值,以防止浪費(fèi)用于
不斷增長(zhǎng)和收縮堆的VM資源鳞尔。
第三點(diǎn)將初始堆大小設(shè)置和最大堆大小相同嬉橙,可以減少JVM重新分配內(nèi)存,伸縮J堆大小時(shí)候的GC壓力寥假,在線上實(shí)際運(yùn)行的時(shí)候給-Xms和-Xmx參數(shù)均設(shè)置成2048m市框,可是使用htop命令查看進(jìn)程消耗的內(nèi)存時(shí)候,發(fā)現(xiàn)只使用了1098m糕韧。Xms是設(shè)置初始堆和最小堆的大小枫振,難道出問(wèn)題了么?使用jmap命令輸出堆的實(shí)際情況萤彩,如下:
[root@pc ~]# jmap -heap 15189
Attaching to process ID 15189, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 715653120 (682.5MB)
MaxNewSize = 715653120 (682.5MB)
OldSize = 1431830528 (1365.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 677380096 (646.0MB)
used = 359083872 (342.4490661621094MB)
free = 318296224 (303.5509338378906MB)
53.01069135636368% used
From Space:
capacity = 18874368 (18.0MB)
used = 8472112 (8.079635620117188MB)
free = 10402256 (9.920364379882812MB)
44.886864556206596% used
To Space:
capacity = 18350080 (17.5MB)
used = 0 (0.0MB)
free = 18350080 (17.5MB)
0.0% used
PS Old Generation
capacity = 1431830528 (1365.5MB)
used = 45051784 (42.96472930908203MB)
free = 1386778744 (1322.535270690918MB)
3.14644667221399% used
31785 interned Strings occupying 3781800 bytes.
查看以上信息發(fā)現(xiàn)粪滤,堆分配大小沒(méi)有問(wèn)題的呀!這是為什么呢雀扶。杖小。查了一會(huì)資料,JVM啟動(dòng)的時(shí)候愚墓,將虛擬地址與本機(jī)物理內(nèi)存地址進(jìn)行映射予权,實(shí)際運(yùn)行的時(shí)候并沒(méi)有使用到2048m內(nèi)存,在堆內(nèi)存不夠用的時(shí)候回向操作系統(tǒng)申請(qǐng)物理內(nèi)存浪册,而操作系統(tǒng)htop命令查看的內(nèi)存是RES即實(shí)際占用的物理內(nèi)存扫腺,剛啟動(dòng)的時(shí)候比實(shí)際Xms來(lái)的小。
這里會(huì)帶來(lái)兩個(gè)問(wèn)題:
- 第1次YGC之前Eden區(qū)分配對(duì)象的速度較慢;
- YGC的時(shí)候议经,Young區(qū)的對(duì)象要晉升到Old區(qū)的時(shí)候斧账,這個(gè)時(shí)候需要操作系統(tǒng)真正分配內(nèi)存谴返,這樣就會(huì)加大YGC的停頓時(shí)間;
我們可以給JVM添加-XX:+AlwaysPreTouch這個(gè)參數(shù)優(yōu)化這個(gè)問(wèn)題,不過(guò)這個(gè)參數(shù)在JDK8下有一個(gè)副作用會(huì)大大提高啟動(dòng)時(shí)間咧织。
在沒(méi)有配置-XX:+AlwaysPreTouch參數(shù)即默認(rèn)情況下嗓袱,JVM參數(shù)-Xms申明的堆只是在虛擬內(nèi)存中分配,而不是在物理內(nèi)存中分配:它被以一種內(nèi)部數(shù)據(jù)結(jié)構(gòu)的形式記錄习绢,從而避免被其他進(jìn)程使用這些內(nèi)存渠抹。這些內(nèi)存頁(yè)直到被訪問(wèn)時(shí),才會(huì)在物理內(nèi)存中分配闪萄。當(dāng)JVM需要內(nèi)存的時(shí)候梧却,操作系統(tǒng)將根據(jù)需要分配內(nèi)存頁(yè)。
配置-XX:+AlwaysPreTouch參數(shù)后败去,JVM將-Xms指定的堆內(nèi)存中每個(gè)字節(jié)都寫(xiě)入’0’放航,這樣的話,除了在虛擬內(nèi)存中以內(nèi)部數(shù)據(jù)結(jié)構(gòu)保留之外圆裕,還會(huì)在物理內(nèi)存中分配广鳍。并且由于touch這個(gè)行為是單線程的,因此它將會(huì)讓JVM進(jìn)程啟動(dòng)變慢吓妆。所以赊时,要么選擇減少接下來(lái)對(duì)每個(gè)緩存頁(yè)的第一次訪問(wèn)時(shí)間,要么選擇減少JVM進(jìn)程啟動(dòng)時(shí)間
不過(guò)在JDK9中可以使用并行操作行拢,Parallelize Memory Pretouch祖秒。
這里又有疑問(wèn)了,當(dāng)JVM內(nèi)存申請(qǐng)使用完畢后會(huì)返還給物理內(nèi)存嗎舟奠?
- 不會(huì)竭缝,一旦虛擬機(jī)申請(qǐng)分配了物理內(nèi)存,不會(huì)返還沼瘫。
設(shè)置了Xmx參數(shù)歌馍,JVM一定不會(huì)超過(guò)這個(gè)大小么?超過(guò)一定會(huì)OOM或者SOF嗎晕鹊?
- 還有堆外內(nèi)存。暴浦。溅话。
JVM內(nèi)存占用情況
JVM進(jìn)程主要占用內(nèi)存的一些地方,其中JDK8之前JMM模型中共享的永久區(qū)取消變?yōu)樵臻g(metaspace)依舊存放于堆外內(nèi)存之中歌焦。
由以上可以看出飞几,JVM實(shí)際占用的內(nèi)存實(shí)際上包含堆內(nèi)和堆外的,而虛擬機(jī)啟動(dòng)參數(shù)Xms和Xmx限制的是堆的大小独撇,實(shí)際虛擬機(jī)內(nèi)存可能大得多屑墨。
總結(jié)
JVM在默認(rèn)情況下啟動(dòng)的時(shí)候躁锁,
JVM參數(shù)-Xms申明的堆只是在虛擬內(nèi)存中分配,而不是在物理內(nèi)存中分配卵史;
-XX:+AlwaysPreTouch參數(shù)可以申請(qǐng)?zhí)崆罢加脙?nèi)存战转,減少GC消耗,但會(huì)提高啟動(dòng)時(shí)間以躯;
JVM申請(qǐng)的物理內(nèi)存槐秧,一旦申請(qǐng)不會(huì)返還,除非重啟或者關(guān)閉忧设;
JVM實(shí)際占用的內(nèi)存包括堆內(nèi)和堆外內(nèi)存刁标,也就是會(huì)大于Xmx設(shè)置的值。