一、堆外內存組成
通常JVM的參數我們會配置
-Xms 堆初始內存
-Xmx 堆最大內存
-XX:+UseG1GC/CMS 垃圾回收器
-XX:+DisableExplicitGC 禁止顯示GC
-XX:MaxDirectMemorySize 設置最大堆外內存,默認是-xmx-survivor,也就是基本上和-xmx大小相等
-Xss:每個線程的堆棧大小,默認1M
-Xmn: 年輕代大小(eden區+2 survivor)
-XX:newRatio: 4 年輕代與老年代1:4
-XX:survivorRatio: 8Eden區與survivor大小比值
java整個進程占用的內存:
- 堆內存
- metaspace(堆內) JDK8使用metaspace來替代了permsize:永久代大小
- 堆外內存使用
- 線程棧空間
堆外內存回收: 堆外內存的回收是通過system.gc()來的,依賴於目前的gc機制。
通常是通過DirectByteBuffer對象來分配堆外內存,gc的時候就是判斷這個對象是否被引用,來決定是否回收。
二、堆外內存參數配置
-XX:InitialCodeCacheSize=64M \
-XX:CodeCacheExpansionSize=1M \
-XX:CodeCacheMinimumFreeSpace=1M \
-XX:ReservedCodeCacheSize=200M \
-XX:MinMetaspaceExpansion=1M \
-XX:MaxMetaspaceExpansion=8M \
-XX:MaxDirectMemorySize=96M \
-XX:CompressedClassSpaceSize=256M \
三、問題排查
3.1、首先確認堆占用
1、用jmap,jmap 查看heap內存使用情況
jmap -heap pid
可以查看到MetaspaceSize,CompressedClassSpaceSize,MaxMetaSize
jmap和jdk版本有關系,有些jdk版本會查看不到內存信息,可以使用jstat來查看統計信息
2、jstat 收集統計信息
jstat -gc pid 1000 S0C/S0U S1C/S1U EC/EU CCSC/CCSU YGC/YGCT FGC/FCGT GCT
survivor0容量和使用 survivor1容量和使用 Eden jdk8是meta,以前應該是PC,PC young gc次數和耗時 full gc次數和耗時 total gc時間
如果能排除掉heap的問題,就要分析堆外內存情況了。
3.2、分析堆外情況
NMT(native memory tracking)
使用
在JVM參數中添加 -XX:NativeMemoryTracking=[off | summary | detail]
-XX:NativeMemoryTracking=detail
在JVM運行過程中,使用jcmd獲取相關信息
jcmd pid VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]
jcmd pid VM.native_memory detail
baseline個基准,之后會輸出diff參數,來和這個基線版本進行比較,可以兩次的內存差
NMT報告會顯示內存使用情況
類別 含義
Java Heap 堆大小
Thread 線程
Thread Stack 線程棧
NMT可以得到線程棧大小,排除棧空間影響。
pmap 查看進程內存地址空間
pmap -x pid | sort xx
可以結合pmap,和nmt得到內存地址空間。和堆外占用情況了。
接下來需要做的就是分析堆外內存的內容了。
gdb dump查看內存空間內容
gdb dump查看內存空間內容
(gdb) dump binary memory ./file BEGIN_ADDRESS END_ADDRESS
將內存內容dump到文件中,就可以查看到文件中的內容了。
但是這種方式不直觀,所以可以使用其他工具
gperf
google的,使用gperf2.5即可,網上很多安裝都說一定要安裝libunwind,其實都是瞎抄抄,老版本確實需要,2.5的版本不需要了。
https://blog.csdn.net/unix21/article/details/79161250
另外一個注意點就是雖然heap文件只有1M,但是可以分析出堆外內存的大小。
不過我在實際使用過程中,gperf並沒有分析出實際的堆外內存情況,通過pmap可以看出堆外內存占用有幾個G,但是gperf始終只有200M
Jemalloc
https://github.com/jemalloc/jemalloc/releases
安裝
./configurate –enable-prof
make
sudo make install
配置
export LD_PRELOAD=/usr/local/lib/libjemalloc.so
export MALLOC_CONF=prof:true,lg_prof_interval:31,lg_prof_sample:17,prof_prefix:/output/jeprof
https://github.com/jemalloc/jemalloc/wiki/Getting-Started
環境:基於B\S的點子考試系統,為了發現客戶端能實時地從服務端接收考試數據,系統使用了逆向AJAX技術(也稱Comet或Server Side Push),選用CometD1.1.1作為服務端推送框架,服務器是Jetty7.1.4,硬件為一台普通PC機,Core i5 CPU,
4G內存,運行32位Windows操作系統。
說明:測試期間發現服務端不定時拋出內存溢出異常,服務器不一定每次都會出現異常,但是假如正式考試時奔潰一次,那估計整場考試都會全亂套,網站管理員嘗試過把堆開到最大,32位系統最多到1.6GB基本無法再加大了,而且開大量也基本沒效果,拋出
內存溢出異常好像更加繁瑣了。加入-XX:+HeapDumpOnOutOfMemoryError,居然也沒有任何反應,拋出內存溢出異常時什么文件都沒產生。無奈之下只好掛着jstat使勁盯屏幕,發現GC並不頻繁,Eden區,Survivor區,老年代及擁擠代內存全部
表示"情緒穩定,壓力不大",但是照樣不停的拋出內存溢出異常,管理員鴨梨很大。最后,在內存溢出后從系統日志中找到異常堆棧。
分析:大家都知道操作系統對每個進程能管理的內存是有限的,這台服務器使用的32位Windows平台的限制是2GB,其中給了Java堆1.6GB,而Direct Memory 並不算在1.6GB的堆之內,因此它只能在剩余的0.4GB空間分出一部分。在此應用中導致內
存溢出的關鍵是:垃圾收集進行時,虛擬機雖然會對Direct Memory進行回收,但是Direct Memory 卻不能像新生代和老年代那樣,發現空間不足了就通知收集器進行垃圾回收,他只能等到拋出內存溢出異常時,先catch掉,再在catch塊里面“大喊”
“System.gc”.要是虛擬機還是不聽(如:打開了-XX:+DisableExplicitGC開關),那就只能眼睜睜地看着堆中還有許多空閑內存,自己卻不得不拋出內存異常了。而本案例中使用的Comet1.1.1框架,正好有大量的NIO操作需要用到Direct Memory。
總結:從實踐經驗來看,除了java堆和永久代之外,我們注意到下面這些區域也會占用較多的內存,這里所有的內存總和會受到操作系統進程最大內存的限制:
1.Direct Memory:可以通過-XX:MaxDirectMemorySize調整大小,內存不足時拋出OutOfMemoryError或OutOfMemoryError:Direct buffer memory。
2.線程堆棧:可通過-Xss調整大小內存不足時拋出StackoverflowErroe(縱向無法分配,即無法分配新的棧幀)或OutOfMemoryError:unable to create new native thread(橫向無法分配,即無法建立新的線程)。
3.Socket緩存區:每個Socket連接都Receive和Send兩個緩存區,分別占大約37KB和25KB的內存,連接多的話這塊內存占用也比較可觀。如果無法分配,則可能會拋出IOException:Too many open files異常。
4.JNI代碼:如果代碼中使用JNI調用本地庫,那么本地庫使用內存也不在堆中
5.虛擬機和GC:虛擬機和GC的代碼執行也要消耗一定的內存。