線上問題排查異聞錄

如何解決堆內(nèi)存溢出問題

OOM有很多種情況啊,這里就先講解最常見也是最容易觀測(cè)的java.lang.OutOfMemoryError: Java heap space,也就是堆內(nèi)存溢出。

發(fā)現(xiàn)

啟動(dòng)Java程序的時(shí)候,最好參數(shù)加上-XX:+HeapDumpOnOutOfMemoryError,該參數(shù)不影響程序運(yùn)行,運(yùn)行時(shí)沒有任何開銷,只有OOM時(shí)會(huì)自動(dòng)生成Java Heap Dump(特定時(shí)刻 JVM 內(nèi)存中所有對(duì)象的快照)。該文件默認(rèn)會(huì)在運(yùn)行應(yīng)用程序同級(jí)目錄下生成一個(gè)格式為hprof的文件,當(dāng)然也可以使用參數(shù)-XX:HeapDumpPath=/data指定生成到data文件夾下。

這里說(shuō)一下我對(duì)于Java程序運(yùn)行添加參數(shù)的一些理解,這是我項(xiàng)目的一個(gè)常規(guī)啟動(dòng)命令,java -javaagent:/usr/local/app/skywalking_agent_zy/skywalking-agent.jar -Dskywalking.agent.service_name=appName?Dskywalking.collector.backendservice={appName} -Dskywalking.collector.backend_service=appName?Dskywalking.collector.backendservice={skywalkingIp}:skywalkingPort?Dskywalking.plugin.toolkit.log.grpc.reporter.serverhost={skywalkingPort} -Dskywalking.plugin.toolkit.log.grpc.reporter.server_host=skywalkingPort?Dskywalking.plugin.toolkit.log.grpc.reporter.serverhost={skywalkingIp} jvmoption?Dserver.port=8080?Denv=jvmoption -Dserver.port=8080 -Denv=jvmoption?Dserver.port=8080?Denv={env} -jar /usr/local/app/app.jar。${}占位符這里是在DevOps上面配的,當(dāng)然大家也沒必要關(guān)注,嘻嘻。這里這個(gè)env是公司框架讓配的環(huán)境參數(shù),前面Javaagent一堆參數(shù)都是skywalking要用的。

除開這些客制化的東西,對(duì)于普通的應(yīng)用,一般配置堆大小相同比較好,因?yàn)橥ǔ?lái)說(shuō)一個(gè)服務(wù)器或者容器只會(huì)有一個(gè)Java應(yīng)用,釋放內(nèi)存給誰(shuí)用呢,是吧,沒那必要。JVM初始分配的堆內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64,JVM最大分配的堆內(nèi)存由-Xmx指定,默認(rèn)是物理內(nèi)存的1/4。默認(rèn)空余堆內(nèi)存小于40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制,空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制。因此一般設(shè)置-Xms、-Xmx相等以避免在每次GC后調(diào)整堆的大小。

定位

拿到hprof文件后,可以選用jvisualvm(Jdk8之后不自帶,需要到Github上下載)、JProfiler和IDEA的Profiler(旗艦版才有)打開文件,三者的操作邏輯都是類似的,目前我用的最舒服的是JProfiler,以下就拿JProfiler截圖舉例。

導(dǎo)入hprof文件到JProfiler之后經(jīng)過(guò)解析,默認(rèn)會(huì)跳到該界面,這里直接選上面的最大對(duì)象,繼續(xù)解析。

這里右鍵選定比較大的對(duì)象后會(huì)彈出這樣一個(gè)框,選擇引用-傳入引用。為啥是傳入引用呢,因?yàn)槲覀円覇栴}的源頭啊,哪里來(lái)的才是比較重要的。

找到對(duì)應(yīng)堆棧信息,點(diǎn)擊顯示更多,即可發(fā)現(xiàn)帶惡人。

以上就是一次完整的查詢過(guò)程,如果點(diǎn)開發(fā)現(xiàn)都是差不多的內(nèi)容,為了少點(diǎn)幾次,保護(hù)鼠標(biāo),我建議可以換成旭日?qǐng)D更加便捷地查看

可以觀察到相對(duì)類型地這個(gè)對(duì)象比較多啊,這里點(diǎn)擊一下這塊進(jìn)入內(nèi)部查詢

如何解決CPU占用高問題

CPU占用高的問題就沒有掛了之后自動(dòng)dump文件的好事了。這時(shí)候需要善用jstack、監(jiān)控和Arthas等工具。

發(fā)現(xiàn)

正常來(lái)說(shuō),咱們會(huì)有監(jiān)控軟件去監(jiān)控服務(wù)器的一些性能指標(biāo),我這用的是Prometheus+Grafana,非常大眾哈。

如圖可以觀察到一個(gè)服務(wù)器CPU占用的折線圖,配合告警可以及時(shí)通知相關(guān)人員定位問題。

定位-傳統(tǒng)武學(xué)

通過(guò)上面地監(jiān)控及時(shí)發(fā)現(xiàn)問題,接下來(lái)就該上手具體的操作了。

  1. top -o %CPU,Linux上按CPU從大到小排序,找到占用最多的PID(這里假設(shè)是Java應(yīng)用)
  2. jstack pid > thread.txt,通過(guò)jstack命令打印當(dāng)前Java應(yīng)用的堆棧信息
  3. top -Hp pid,通過(guò)該命令觀察此pid進(jìn)程中所有線程的CPU占用
  4. 找到線程pid,通過(guò)命令printf '%x\n' pid得到轉(zhuǎn)換為16進(jìn)制的nid
  5. 在jstack獲得的文件thread.txt中,找到nid對(duì)應(yīng)的線程堆棧信息,找到對(duì)應(yīng)代碼塊即可
  6. 通常除了CPU占用過(guò)高的線程,還需要重點(diǎn)關(guān)注線程狀態(tài)為BLOCKED、WAITING和TIMED_WAITING的部分

定位-新派寶典

我一開始接觸的也是傳統(tǒng)武學(xué),啪啪啪一堆命令敲得也是非常麻煩嗷,那有沒有開箱即用的好東西呢。沒錯(cuò),那肯定是有的,就是大名鼎鼎的Arthas啦。

  1. 下載Arthas.jar,curl -O arthas.aliyun.com/arthas-boot…
  2. 運(yùn)行java -jar arthas-boot.jar并選擇需要監(jiān)聽的Java應(yīng)用,圖形化很贊
  3. 輸入命令dashboard打開看板,隨時(shí)監(jiān)控,默認(rèn)5000ms一刷
  4. 針對(duì)上面CPU問題,直接選擇Thread系列命令

效果如下,牛中牛中牛,解放雙手。相比jstack輸出的文件,甚至多了cpuUsage這個(gè)參數(shù),更加直觀。

Arthas還有很多別的牛逼功能,不僅僅是Jdk工具的一個(gè)打包,更是對(duì)前者進(jìn)行了易用性上的極大優(yōu)化,同時(shí)也提供了很多新功能,要知道這玩意才一百多KB啊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容