掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
本文轉(zhuǎn)載自微信公眾號(hào)「小姐姐味道」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系小姐姐味道公眾號(hào)。

對(duì)于內(nèi)存問(wèn)題排查來(lái)說(shuō),搞理論的痛苦,搞實(shí)踐的也痛苦,沒(méi)有一片清凈之地。
why?因?yàn)閮?nèi)存溢出是Java碼農(nóng)永遠(yuǎn)的傷。
溢出有很多種解釋,有精滿自溢,有緩沖區(qū)溢出攻擊,還有另外一種叫做領(lǐng)導(dǎo)的溢出。不知道什么叫作溢出理論,xjjdog在此普及一下。
《領(lǐng)導(dǎo)看了會(huì)炸毛的溢出理論》
內(nèi)存溢出什么最重要?其實(shí),內(nèi)存溢出就像是一場(chǎng)交通事故。事故的發(fā)生方,就是具體的服務(wù);事故的處理方,就是相關(guān)的程序員。其中有一個(gè)最重要的環(huán)節(jié),就是在事故現(xiàn)場(chǎng)需要拍照取證。
如果沒(méi)有照片沒(méi)有行車記錄儀沒(méi)有證據(jù),就只能靠那張嘴,怎么說(shuō)都是不可信的。
**這句話很重要很重要:內(nèi)存問(wèn)題排查什么最重要?當(dāng)然是信息收集,留下一些為我們的排查提供支持的依據(jù)。**千萬(wàn)不要舍本逐末,對(duì)內(nèi)存問(wèn)題排查本身感興趣,那是自虐行為。
有很多工具可以幫助我們定位問(wèn)題,但前提是你得把它留下來(lái)。下面這篇文章是xjjdog很久之前留下來(lái)的,由于標(biāo)題的緣故,你可能忽略了,但這些工具能夠快速幫我們定位問(wèn)題。
- ss -antp > $DUMP_DIR/ss.dump 2>&1
- netstat -s > $DUMP_DIR/netstat-s.dump 2>&1
- top -Hp $PID -b -n 1 -c > $DUMP_DIR/top-$PID.dump 2>&1
- sar -n DEV 1 2 > $DUMP_DIR/sar-traffic.dump 2>&1
- lsof -p $PID > $DUMP_DIR/lsof-$PID.dump
- iostat -x > $DUMP_DIR/iostat.dump 2>&1
- free -h > $DUMP_DIR/free.dump 2>&1
- jstat -gcutil $PID > $DUMP_DIR/jstat-gcutil.dump 2>&1
- jstack $PID > $DUMP_DIR/jstack.dump 2>&1
- jmap -histo $PID > $DUMP_DIR/jmap-histo.dump 2>&1
- jmap -dump:format=b,file=$DUMP_DIR/heap.bin $PID > /dev/null 2>&1
GC日志配置
但并不是每次出現(xiàn)故障,你都在機(jī)器的身邊。靠人工也不能保證實(shí)時(shí)性。所以,強(qiáng)烈建議你把GC日志輸出的詳細(xì)一些,那么出現(xiàn)問(wèn)題的時(shí)候就舒坦一些。
實(shí)際上,這個(gè)要求在我看來(lái)是強(qiáng)制的。
很多同學(xué)上來(lái)就說(shuō),我的內(nèi)存溢出了。但你和它要一些日志信息,要堆棧,要現(xiàn)場(chǎng)保存的快照。都沒(méi)有。這就是純粹來(lái)搞笑的。
下面是JDK8或者以下的GC日志參數(shù),可以看到還是很長(zhǎng)的。
- #!/bin/sh
- LOG_DIR="/tmp/logs"
- JAVA_OPT_LOG=" -verbose:gc"
- JAVA_OPT_LOG="${JAVA_OPT_LOG} -XX:+PrintGCDetails"
- JAVA_OPT_LOG="${JAVA_OPT_LOG} -XX:+PrintGCDateStamps"
- JAVA_OPT_LOG="${JAVA_OPT_LOG} -XX:+PrintGCApplicationStoppedTime"
- JAVA_OPT_LOG="${JAVA_OPT_LOG} -XX:+PrintTenuringDistribution"
- JAVA_OPT_LOG="${JAVA_OPT_LOG} -Xloggc:${LOG_DIR}/gc_%p.log"
- JAVA_OPT_OOM=" -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR} -XX:ErrorFile=${LOG_DIR}/hs_error_pid%p.log "
- JAVA_OPT="${JAVA_OPT_LOG} ${JAVA_OPT_OOM}"
- JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
下面是JDK9及其以上的日志配置。可以看到它的配置方式全變了,而且不向下兼容。Java搞的這個(gè)變化還是挺蛋疼的。
- #!/bin/sh
- LOG_DIR="/tmp/logs"
- JAVA_OPT_LOG=" -verbose:gc"
- JAVA_OPT_LOG="${JAVA_OPT_LOG} -Xlog:gc,gc+ref=debug,gc+heap=debug,gc+age=trace:file=${LOG_DIR}/gc_%p.log:tags,uptime,time,level"
- JAVA_OPT_LOG="${JAVA_OPT_LOG} -Xlog:safepoint:file=${LOG_DIR}/safepoint_%p.log:tags,uptime,time,level"
- JAVA_OPT_OOM=" -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR} -XX:ErrorFile=${LOG_DIR}/hs_error_pid%p.log "
- JAVA_OPT="${JAVA_OPT_LOG} ${JAVA_OPT_OOM}"
- JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
- echo $JAVA_OPT
一旦發(fā)現(xiàn)了問(wèn)題,就可以拿GC日志來(lái)快速定位堆內(nèi)問(wèn)題。但是并不是讓你一行行去看,那太低效了。因?yàn)槿罩究赡軙?huì)很長(zhǎng)很長(zhǎng),而且也不一定看得懂。這個(gè)時(shí)候,就可以使用一些在線工具輔助解決。我經(jīng)常使用的是gceasy,下面是它的一張截圖。
- http://gceasy.io
有了GC日志還不行,因?yàn)樗鼉H僅是記錄了堆空間的一些變化,至于操作系統(tǒng)的一些資源變動(dòng),它是無(wú)從知曉的。所以,如果你有一個(gè)監(jiān)控系統(tǒng)的話,在尋找問(wèn)題的時(shí)候也能幫到忙。從下圖可以看到系統(tǒng)資源的一些變動(dòng)。
溢出示例
堆溢出
代碼。
日志。
- java -Xmx20m -Xmn4m -XX:+HeapDumpOnOutOfMemoryError - OOMTest
- [18.386s][info][gc] GC(10) Concurrent Mark 5.435ms
- [18.395s][info][gc] GC(12) Pause Full (Allocation Failure) 18M->18M(19M) 10.572ms
- [18.400s][info][gc] GC(13) Pause Full (Allocation Failure) 18M->18M(19M) 5.348ms
- Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
- at OldOOM.main(OldOOM.java:20)
jvisualvm的反應(yīng)。
元空間溢出
代碼。
日志。
- java -Xmx20m -Xmn4m -XX:+HeapDumpOnOutOfMemoryError -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=16M MetaspaceOOMTest
- 6.556s][info][gc] GC(30) Concurrent Cycle 46.668ms
- java.lang.OutOfMemoryError: Metaspace
- Dumping heap to /tmp/logs/java_pid36723.hprof ..
jvisualvm的反應(yīng)。
直接內(nèi)存溢出
代碼。
日志。
- java -XX:MaxDirectMemorySize=10M -Xmx10M OffHeapOOMTest
- Exception in thread "Thread-2" java.lang.OutOfMemoryError: Direct buffer memory
- at java.nio.Bits.reserveMemory(Bits.java:694)
- at java.nio.DirectByteBuffer.
(DirectByteBuffer.java:123) - at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
- at OffHeapOOMTest.oom(OffHeapOOMTest.java:27)...
棧溢出
代碼。
日志。
- java -Xss128K StackOverflowTest
- Exception in thread "main" java.lang.StackOverflowError
- at java.io.PrintStream.write(PrintStream.java:526)
- at java.io.PrintStream.print(PrintStream.java:597)
- at java.io.PrintStream.println(PrintStream.java:736)
- at StackOverflowTest.a(StackOverflowTest.java:5)
哪些代碼容易出現(xiàn)問(wèn)題
忘記重寫(xiě)hashCode和equals
看下面的代碼。由于沒(méi)有重寫(xiě)Key類的hashCode和equals方法。造成了放入HashMap的所有對(duì)象,都無(wú)法被取出來(lái)。它們和外界失聯(lián)了。
下面這篇文章詳細(xì)的描述了它的原理。
結(jié)果集失控
不要覺(jué)得這段代碼可笑。在實(shí)際工作中的review中,xjjdog不止一次發(fā)現(xiàn)這種蛋疼的代碼。這有可能是趕工期,也有可能是剛學(xué)會(huì)寫(xiě)Java。這行代碼有很大的可能性踩坑。
條件失控
代碼。與之類似的就是條件失控,當(dāng)某個(gè)條件不滿足的時(shí)候,將會(huì)造成結(jié)果集的失控。大家可以看下面的代碼,fullname 和 other為空的時(shí)候,會(huì)出現(xiàn)什么后果?
萬(wàn)能參數(shù)
還有的同學(xué)使用各種Object和HashMap來(lái)進(jìn)行信息交換。這種代碼正常運(yùn)行的時(shí)候沒(méi)什么問(wèn)題,但一旦出錯(cuò),幾乎無(wú)法排查。排查參數(shù)、排查堆棧、排查調(diào)用鏈,全部失效。
一些預(yù)防的措施
案例分析一
這是最常見(jiàn)的一種情況。了解了這種方式,能夠應(yīng)對(duì)大多數(shù)內(nèi)存溢出和內(nèi)存泄漏問(wèn)題。
現(xiàn)象
信息收集
- wrk -t20 -c20 -d300s http://127.0.0.1:8084/api/test
MAT分析
堆棧文件獲?。?/p>
- jmap -dump:format=b,file=heap.bin 37340
- jhsdb jmap --binaryheap --pid 37340
MAT工具是基于eclipse平臺(tái)開(kāi)發(fā)的,本身是一個(gè)Java程序。分析Heap Dump文件:發(fā)現(xiàn)內(nèi)存創(chuàng)建了大量的報(bào)表對(duì)象。
通過(guò)菜單Find Leaks,一鍵找出黑李逵。
根據(jù)提示向下挖就可以。
解決
分析結(jié)果:
解決方式:
案例分析二
現(xiàn)象
分析
結(jié)論:
案例分析三
分析
下面這些情況都會(huì)造成程序退出而沒(méi)什么響應(yīng)。
解決
發(fā)現(xiàn):
解決:

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流