首先看一下一個(gè)java進(jìn)程的jmap輸出:
代碼如下:
[lex@chou ~]$ jmap -heap 837
Attaching to process ID 837, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.10-b01
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 4294967296 (4096.0MB)
NewSize = 1310720 (1.25MB)
MaxNewSize = 17592186044415 MB
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 21757952 (20.75MB)
MaxPermSize = 85983232 (82.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 41025536 (39.125MB)
used = 18413552 (17.560531616210938MB)
free = 22611984 (21.564468383789062MB)
44.883147900858624% used
From Space:
capacity = 4325376 (4.125MB)
used = 3702784 (3.53125MB)
free = 622592 (0.59375MB)
85.60606060606061% used
To Space:
capacity = 4521984 (4.3125MB)
used = 0 (0.0MB)
free = 4521984 (4.3125MB)
0.0% used
PS Old Generation
capacity = 539820032 (514.8125MB)
used = 108786168 (103.74657440185547MB)
free = 431033864 (411.06592559814453MB)
20.152302906758376% used
PS Perm Generation
capacity = 85983232 (82.0MB)
used = 60770232 (57.95500946044922MB)
free = 25213000 (24.04499053955078MB)
70.67684080542588% used
然后再用ps看看:
代碼如下:
[lex@chou ~]$ ps -p 837 -o vsz,rss
VSZ RSS
7794992 3047320
關(guān)于這里的幾個(gè)generation網(wǎng)上資料一大把就不細(xì)說了,這里算一下求和可以得知前者總共給Java環(huán)境分配了644M的內(nèi)存,而ps輸出的VSZ和RSS分別是7.4G和2.9G,這到底是怎么回事呢?
前面jmap輸出的內(nèi)容里,MaxHeapSize 是在命令行上配的,-Xmx4096m,這個(gè)java程序可以用到的最大堆內(nèi)存。
VSZ是指已分配的線性空間大小,這個(gè)大小通常并不等于程序?qū)嶋H用到的內(nèi)存大小,產(chǎn)生這個(gè)的可能性很多,比如內(nèi)存映射,共享的動(dòng)態(tài)庫,或者向系統(tǒng)申請(qǐng)了更多的堆,都會(huì)擴(kuò)展線性空間大小,要查看一個(gè)進(jìn)程有哪些內(nèi)存映射,可以使用 pmap 命令來查看:
代碼如下:
[lex@chou ~]$ pmap -x 837
837: java
Address Kbytes RSS Dirty Mode Mapping
0000000040000000 36 4 0 r-x-- java
0000000040108000 8 8 8 rwx-- java
00000000418c9000 13676 13676 13676 rwx-- [ anon ]
00000006fae00000 83968 83968 83968 rwx-- [ anon ]
0000000700000000 527168 451636 451636 rwx-- [ anon ]
00000007202d0000 127040 0 0 ----- [ anon ]
...
...
00007f55ee124000 4 4 0 r-xs- az.png
00007fff017ff000 4 4 0 r-x-- [ anon ]
ffffffffff600000 4 0 0 r-x-- [ anon ]
---------------- ------ ------ ------
total kB 7796020 3037264 3023928
這里可以看到很多anon,這些表示這塊內(nèi)存是由mmap分配的。
RSZ是Resident Set Size,常駐內(nèi)存大小,即進(jìn)程實(shí)際占用的物理內(nèi)存大小, 在現(xiàn)在這個(gè)例子當(dāng)中,RSZ和實(shí)際堆內(nèi)存占用差了2.3G,這2.3G的內(nèi)存組成分別為:
JVM本身需要的內(nèi)存,包括其加載的第三方庫以及這些庫分配的內(nèi)存
NIO的DirectBuffer是分配的native memory
內(nèi)存映射文件,包括JVM加載的一些JAR和第三方庫,以及程序內(nèi)部用到的。上面 pmap 輸出的內(nèi)容里,有一些靜態(tài)文件所占用的大小不在Java的heap里,因此作為一個(gè)Web服務(wù)器,趕緊把靜態(tài)文件從這個(gè)Web服務(wù)器中人移開吧,放到nginx或者CDN里去吧。
JIT, JVM會(huì)將Class編譯成native代碼,這些內(nèi)存也不會(huì)少,如果使用了Spring的AOP,CGLIB會(huì)生成更多的類,JIT的內(nèi)存開銷也會(huì)隨之變大,而且Class本身JVM的GC會(huì)將其放到Perm Generation里去,很難被回收掉,面對(duì)這種情況,應(yīng)該讓JVM使用ConcurrentMarkSweep GC,并啟用這個(gè)GC的相關(guān)參數(shù)允許將不使用的class從Perm Generation中移除, 參數(shù)配置: -XX:+UseConcMarkSweepGC -X:+CMSPermGenSweepingEnabled -X:+CMSClassUnloadingEnabled,如果不需要移除而Perm Generation空間不夠,可以加大一點(diǎn): -X:PermSize=256M -X:MaxPermSize=512M
JNI,一些JNI接口調(diào)用的native庫也會(huì)分配一些內(nèi)存,如果遇到JNI庫的內(nèi)存泄露,可以使用valgrind等內(nèi)存泄露工具來檢測
線程棧,每個(gè)線程都會(huì)有自己的??臻g,如果線程一多,這個(gè)的開銷就很明顯了
jmap/jstack 采樣,頻繁的采樣也會(huì)增加內(nèi)存占用,如果你有服務(wù)器健康監(jiān)控,記得這個(gè)頻率別太高,否則健康監(jiān)控變成致病監(jiān)控了。
關(guān)于JVM的幾個(gè)GC堆和GC的情況,可以用jstat來監(jiān)控,例如監(jiān)控進(jìn)程837每隔1000毫秒刷新一次,輸出20次:
代碼如下:
[lex@chou ~]$ jstat -gcutil 837 1000 20
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 80.43 24.62 87.44 98.29 7101 119.652 40 19.719 139.371
0.00 80.43 33.14 87.44 98.29 7101 119.652 40 19.719 139.371
幾個(gè)字段分別含義如下:
S0
年輕代中第一個(gè)survivor(幸存區(qū))已使用的占當(dāng)前容量百分比
S1
年輕代中第二個(gè)survivor(幸存區(qū))已使用的占當(dāng)前容量百分比
E
年輕代中Eden(伊甸園)已使用的占當(dāng)前容量百分比
O
old代已使用的占當(dāng)前容量百分比
P
perm代已使用的占當(dāng)前容量百分比
YGC
從應(yīng)用程序啟動(dòng)到采樣時(shí)年輕代中g(shù)c次數(shù)
YGCT
從應(yīng)用程序啟動(dòng)到采樣時(shí)年輕代中g(shù)c所用時(shí)間(s)
FGC
從應(yīng)用程序啟動(dòng)到采樣時(shí)old代(全gc)gc次數(shù)
FGCT
從應(yīng)用程序啟動(dòng)到采樣時(shí)old代(全gc)gc所用時(shí)間(s)
GCT
從應(yīng)用程序啟動(dòng)到采樣時(shí)gc用的總時(shí)間(s)
結(jié)論
因此如果正常情況下jmap輸出的內(nèi)存占用遠(yuǎn)小于 RSZ,可以不用太擔(dān)心,除非發(fā)生一些嚴(yán)重錯(cuò)誤,比如PermGen空間滿了導(dǎo)致OutOfMemoryError發(fā)生,或者RSZ太高導(dǎo)致引起系統(tǒng)公憤被OOM Killer給干掉,就得注意了,該加內(nèi)存加內(nèi)存,沒錢買內(nèi)存加交換空間,或者按上面列的組成部分逐一排除。
這幾個(gè)內(nèi)存指標(biāo)之間的關(guān)系是:VSZ >> RSZ >> Java程序?qū)嶋H使用的堆大小
更多信息請(qǐng)查看IT技術(shù)專欄