1、使用dmesg命令查看系統日志
dmesg |grep -E ‘kill|oom|out of memory’,可以查看操作系統啟動后的系統日志,這里就是查看跟內存溢出相關聯的系統日志。
2、這時候,需要啟動項目,使用ps命令查看進程
ps -aux|grep java命令查看一下你的java進程,就可以找到你的java進程的進程id。
3、接着使用top命令
top命令顯示的結果列表中,會看到%MEM這一列,這里可以看到你的進程可能對內存的使用率特別高。以查看正在運行的進程和系統負載信息,包括cpu負載、內存使用、各個進程所占系統資源等。
4、使用jstat命令
用jstat -gcutil 20886 1000 10命令,就是用jstat工具,對指定java進程(20886就是進程id,通過ps -aux | grep java命令就能找到),按照指定間隔,看一下統計信息,這里會每隔一段時間顯示一下,包括新生代的兩個S0、s1區、Eden區,以及老年代的內存使用率,還有young gc以及full gc的次數。
使用 jstat -gcutil 8968 500 5 表示每500毫秒打印一次Java堆狀況(各個區的容量、使用容量、gc時間等信息),打印5次
例如:
看到的東西類似下面那樣:
S0 S1 E O YGC FGC
26.80 0.00 10.50 89.90 86 954
其實如果大家了解原理,應該知道,一般來說大量的對象涌入內存,結果始終不能回收,會出現的情況就是,快速撐滿年輕代,然后young gc幾次,根本回收不了什么對象,導致survivor區根本放不下,然后大量對象涌入老年代。老年代很快也滿了,然后就頻繁full gc,但是也回收不掉。
然后對象持續增加不就oom了,內存放不下了,爆了唄。
所以jstat先看一下基本情況,馬上就能看出來,其實就是大量對象沒法回收,一直在內存里占據着,然后就差不多內存快爆了。
5、使用jmap命令查看
執行jmap -histo pid可以打印出當前堆中所有每個類的實例數量和內存占用,如下,class name是每個類的類名([B是byte類型,[C是char類型,[I是int類型),bytes是這個類的所有示例占用內存大小,instances是這個類的實例數量。
6、把當前堆內存的快照轉儲到dumpfile_jmap.hprof文件中,然后可以對內存快照進行分析
使用jmap -dump:format=b,file=文件名 [pid],就可以把指定java進程的堆內存快照搞到一個指定的文件里去,但是jmap -dump:format其實一般會比較慢一些,也可以用gcore工具來導出內存快照
例如:jmap -dump:format=b,file=D:/log/jvm/dumpfile_jmap.hprof 20886
接着就是可以用MAT工具,或者是Eclipse MAT的內存分析插件,來對hprof文件進行分析,看看到底是哪個王八蛋對象太多了,導致內存溢出了
或者使用jdk的目錄下的bin目錄下的:jvisualvm.exe
8、總結:
一般常見的OOM,要么是短時間內涌入大量的對象,導致你的系統根本支持不住,此時你可以考慮優化代碼,或者是加機器;要么是長時間來看,你的很多對象不用了但是還被引用,就是內存泄露了,你也是優化代碼就好了;這就會導致大量的對象不斷進入老年代,然后頻繁full gc之后始終沒法回收,就撐爆了
要么是加載的類過多,導致class在永久代理保存的過多,始終無法釋放,就會撐爆
我這里可以給大家最后提一點,人家肯定會問你有沒有處理過線上的問題,你就說有,最簡單的,你說有個小伙子用了本地緩存,就放map里,結果沒控制map大小,可以無限擴容,最終導致內存爆了,后來解決方案就是用了一個ehcache框架,自動LRU清理掉舊數據,控制內存占用就好了。
另外,務必提到,線上jvm必須配置-XX:+HeapDumpOnOutOfMemoryError,-XX:HeapDumpPath=/path/heap/dump。因為這樣就是說OOM的時候自動導出一份內存快照,你就可以分析發生OOM時的內存快照了,到底是哪里出現的問題。
9、修改代碼調優,修改jvm配置調優,部署接口壓測
代碼進行優化、根據壓測的情況去進行一定的jvm參數的調優,一個系統的QPS,一個是系統的接口的性能,壓測到一定程度的時候 ,機器的cpu、內存、io、磁盤的一些負載情況,jvm的表現
10、流程
附加:系統頻繁full gc:
比OOM稍微好點的是頻繁full gc,如果OOM就是系統自動就掛了,很慘,你絕對是超級大case,但是頻繁full gc會好多,其實就是表現為經常請求系統的時候,很卡,一個請求卡半天沒響應,就是會覺得系統性能很差。
首先,你必須先加上一些jvm的參數,讓線上系統定期打出來gc的日志:
-XX:+PrintGCTimeStamps
-XX:+PrintGCDeatils
-Xloggc:<filename>
這樣如果發現線上系統經常卡頓,可以立即去查看gc日志,大概長成這樣:
如果要是發現每次Full GC過后,ParOldGen就是老年代老是下不去,那就是大量的內存一直占據着老年代,啥事兒不干,回收不掉,所以頻繁的full gc,每次full gc肯定會導致一定的stop the world卡頓,這是不可能完全避免的
接着采用跟之前一樣的方法,就是dump出來一份內存快照,然后用Eclipse MAT插件分析一下好了,看看哪個對象量太大了
接着其實就是跟具體的業務場景相關了,要看具體是怎么回事,常見的其實要么是內存泄露,要么就是類加載過多導致永久代快滿了,此時一般就是針對代碼邏輯來優化一下。
給大家還是舉個例子吧,我們線上系統的一個真實例子,大家可以用這個例子在面試里來說,比如說當時我們有個系統,在后台運行,每次都會一下子從mysql里加載幾十萬行數據進來各種處理,類似於定時批量處理,這個時候,如果對幾十萬數據的處理比較慢,就會導致比如幾分鍾里面,大量數據囤積在老年代,然后沒法回收,就會頻繁full gc。
當時我們其實就是根據這個發現了當時兩台機器已經不夠了,因為我們當時線上用了兩台4核8G的虛擬機在跑,明顯不夠了,就要加機器了,所以增加了機器,每台機器處理更少的數據量,那不就ok了,馬上就緩解了頻繁full gc的問題了。