JVM內存監視手段和內存溢出解決方案
引言
本文僅關註一些常見的虛擬機內存監視手段,以及JVM運行時數據區各個部分內存溢出的發生和對應的解決方案,總體來說屬於概括性總結,涉及相對不是很深入,目的是讓自己和其它初學者有一個框架性、概念性的了解,當遇到問題時有跡可循、不至於不知所措。
一、虛擬機內存監視手段
虛擬機常出現的問題包括:內存泄露、內存溢出、頻繁GC導致性能下降等,導致這些問題的原因可以通過下面虛擬機內存監視手段來進行分析,具體實施時可能需要靈活選擇,同時借助兩種甚至更多的手段來共同分析。
比如GC日誌可以分析出哪些GC較為頻繁導致性能下降、是否發生內存泄露。jstat工具和GC日誌類似,同樣可以查看GC情況、分析是否發生內存泄露。判斷發生內存泄露後,可以通過jmap工具和MAT等分析工具的結合查看虛擬機內存快照,分析發生內存泄露的原因。內存溢出快照可以分析出內存溢出發生的原因等。
GC日誌記錄
將JVM每次進行GC的情況記錄下來,通過觀察GC日誌可以看出來GC的頻度、以及每次GC都回收了哪些區域的內存,根據這些信息為依據來調整JVM相關設置,可以減少Minor GC的頻率以及Full GC的次數,還可以判斷是否有內存泄露發生。
下面是常見的GC日誌輸出參數:
u -verbose.gc:顯示GC的操作內容。打開它,可以顯示最忙和最空閑收集行為發生的時間、收集前後的內存大小、收集需要的時間等。
u -XX:+printGCdetails:詳細了解GC中的變化。
u -XX:+PrintGCTimeStamps:了解垃圾收集發生的時間,自JVM啟動以後以秒計量。
u -XX:+PrintHeapAtGC:了解堆的更詳細的信息。
u -Xloggc:[file]:將GC信息輸出到單獨的文件中
jstat:虛擬機統計信息監控工具
實時監視虛擬機運行時的類裝載情況、各部分內存占用情況、GC情況、JIT編譯情況等。
例:每隔250ms查詢一次進程2211的垃圾收集情況,查詢50次
步驟①:jps列出本機所有運行的jvm實例,獲取jvm的pid
步驟②:jstat實時監控gc情況,jstat –gc 2211 250 50
其他參數包括:
-class監視類裝載、卸載數量、總空間以及類裝載所耗費時間
-gccapacity監視內容與-gc相同,輸出主要關註堆各個區域用到的最大、最小空間
-gcutil監視內同與-gc相同,輸出主要關註堆各個區域已使用空間所占總空間百分比
-gcnew監視新生代GC情況
-gcold監視舊生代GC情況
jmap:虛擬機內存映像工具
jmap工具可以讓運行中的JVM生成Dump文件,當JVM內存出現問題時可以通過jmap生成快照,分析整個堆,主要經歷兩個步驟:
步驟1:jps列出本機所有運行的jvm實例,獲取jvm的pid
步驟2:使用jmap命令將指定JVM快照導出為dump文件
jmap -dump:format=b,file=path/heap.bin PID
獲得JVM快照的dump文件之後,可以通過MAT工具進行分析。
MAT(MemoryAnalyzer Tool)工具是eclipse的一個插件,使用起來非常方便,尤其是在分析大內存的dump文件時,可以非常直觀的看到各個對象在堆空間中所占用的內存大小、類實例數量、對象引用關系、利用OQL對象查詢,以及可以很方便的找出對象GC Roots的相關信息,最吸引人的是能夠快速為開發人員生成內存泄露報表,方便定位和分析問題。
除此之外,jmap還可以查詢finalize執行隊列、Java堆和持久代的詳細信息,比如空間使用率,當前使用的是哪種收集器等。
內存溢出快照生成
通過設置JVM參數,可以讓虛擬機發生OutOfMemoryError(OOM)內存溢出時自動生成dump文件,通過分析dump文件查看內存使用情況可以找到內存溢出發生的原因:
-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/path/to/heap/dump
獲得JVM快照的dump文件之後,可以通過MAT工具進行分析。
二、運行時數據區內存溢出
JVM運行時數據區分為以下幾個部分:
其中方法區和堆是所有工作線程共享的,而棧、程序計數器和本地方法棧是線程私有的。
註:圖片轉自網絡
1.程序計數器
作用:指向當前線程下一條需要執行的字節碼指令的地址
內存溢出:不會發生
2.虛擬機棧
作用:由棧幀組成、每個棧幀代表一次方法調用,其包含存儲變量表、操作數棧和方法出口三個部分,方法執行完成後該棧幀將被彈出。
內存溢出:StackOverflowError和OutOfMemoryError。
溢出原因:
StackOverflowError:如果請求的棧的深度大於虛擬機所允許的深度,將會拋出這個異常,如果使用虛擬機默認參數,一般達到1000到2000這樣的深度沒有問題。
OutOfMemoryError:因為除掉堆內存和方法區容量,剩下的內存由虛擬機棧和本地方法棧瓜分,如果剩下的內存不足以滿足更多的工作線程的運行、或者不足以拓展虛擬機棧的時候,就會拋出OutOfMemoryError異常。
解決方法:
針對StackOverflowError:
1. 首先棧溢出會輸出異常信息,根據信息查看對應的方法調用是否出現無限調用、或者棧幀過大等代碼邏輯上的問題,通過修改代碼邏輯解決;
2. 如果確確實實需要更大的棧容量,可以檢查並調大棧容量:-Xss16m。
針對OutOfMemoryError:
1. 首先檢查是否創建過多的線程,減少線程數
2. 可以通過“減少最大堆容量”或“減少棧容量”來解決。
3.本地方法棧
作用:與虛擬機棧唯一的不同是虛擬機棧執行的是java方法,而本地方法棧執行的是本地的C/C++方法
內存溢出:StackOverflowError和OutOfMemoryError
溢出原因:同虛擬機棧
解決方法:同虛擬機棧
4.堆
作用:所有線程共享,存放對象實例
內存溢出:OutOfMemoryError:Java heap space
溢出原因:堆中沒有足夠內存完成實例分配,並且無法繼續拓展時
解決方法:
1.內存泄露檢查:首先通過“內存溢出快照 + MAT等分析工具”,分析是否存在內存泄露現象,檢查時可以懷疑的點比如集合、第三方庫如數據庫連接的使用、new關鍵字相關等。
2.如果沒有內存泄露,那麽就是內存溢出,所有對象卻是都還需要存活,這個時候就只能調大堆內存了:-Xms和-Xmx。
5.方法區
作用:所有線程共享,存放已加載的class信息、常量、靜態變量和即時編譯後的代碼
內存溢出:OutOfMemoryError:PermGen space
溢出原因:方法區沒有足夠內存完成內存分配存放運行時新加載的class信息
解決方法:
1. 內存泄露檢查:檢查是否加載過多class文件(jar文件),或者重復加載相同的class文件(jar文件)多次
2. 通過-XX:PermSize=64M -XX:MaxPermSize=128M改大方法區大小
6.運行時常量池
作用:方法區的一部分,存放常量
內存溢出:OutOfMemoryError:PermGen space
溢出原因:方法區沒有足夠的內存完成內存分配,存放運行時新創建的常量,比如String類的intern()方法,其作用是如果常量池已經包含一個相同的字符串,則返回其引用,否則將此String對象包含的字符串添加到常量池中。
解決方法:
1. 內存泄露檢查:檢查是否創建過多常量
2. 通過-XX:PermSize=64M -XX:MaxPermSize=128M改大方法區大小
7.直接內存
作用:不屬於JVM運行時數據區,也不是虛擬機規範中定義的內存區域,JDK1.4引入的NIO中包含通道Channel和緩沖區Buffer,應用程序從通道獲取數據是先經過OS的內核緩沖區,再拷貝至Buffer,因為比較耗時,所以Buffer提供了一種直接操作操作系統緩沖區的方式,即ByteBuffer.allocateDirector(size),這個方法返回DirectByteBuffer應用就是指向這個底層存儲空間關聯的緩沖區,即直接內存(native memory),或者叫堆外內存。
內存溢出:OutOfMemoryError
溢出原因:JVM所需內存 + 直接內存 > 機器物理內存(或操作系統級限制),無法動態拓展
判斷方法:內存泄露檢查:例如內存占用較高,機器性能驟降,但是通過GC信息或者jstat發現GC很少,通過jmap獲得快照分析後也沒發現什麽異常,而程序中又直接或者間接地用到了NIO,那麽和可能就是直接內存泄露了。
解決方法:分析NIO相關的程序邏輯解決。
JVM內存監視手段和內存溢出解決方案