如何解決堆內(nèi)存溢出問題
OOM有很多種情況啊,這里就先講解最常見也是最容易觀測的java.lang.OutOfMemoryError: Java heap space,也就是堆內(nèi)存溢出。
發(fā)現(xiàn)
啟動Java程序的時候,最好參數(shù)加上-XX:+HeapDumpOnOutOfMemoryError,該參數(shù)不影響程序運行,運行時沒有任何開銷,只有OOM時會自動生成Java Heap Dump(特定時刻 JVM 內(nèi)存中所有對象的快照)。該文件默認會在運行應(yīng)用程序同級目錄下生成一個格式為hprof的文件,當然也可以使用參數(shù)-XX:HeapDumpPath=/data指定生成到data文件夾下。

這里說一下我對于Java程序運行添加參數(shù)的一些理解,這是我項目的一個常規(guī)啟動命令,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上面配的,當然大家也沒必要關(guān)注,嘻嘻。這里這個env是公司框架讓配的環(huán)境參數(shù),前面Javaagent一堆參數(shù)都是skywalking要用的。
除開這些客制化的東西,對于普通的應(yīng)用,一般配置堆大小相同比較好,因為通常來說一個服務(wù)器或者容器只會有一個Java應(yīng)用,釋放內(nèi)存給誰用呢,是吧,沒那必要。JVM初始分配的堆內(nèi)存由-Xms指定,默認是物理內(nèi)存的1/64,JVM最大分配的堆內(nèi)存由-Xmx指定,默認是物理內(nèi)存的1/4。默認空余堆內(nèi)存小于40%時,JVM就會增大堆直到-Xmx的最大限制,空余堆內(nèi)存大于70%時,JVM會減少堆直到-Xms的最小限制。因此一般設(shè)置-Xms、-Xmx相等以避免在每次GC后調(diào)整堆的大小。
定位
拿到hprof文件后,可以選用jvisualvm(Jdk8之后不自帶,需要到Github上下載)、JProfiler和IDEA的Profiler(旗艦版才有)打開文件,三者的操作邏輯都是類似的,目前我用的最舒服的是JProfiler,以下就拿JProfiler截圖舉例。

導入hprof文件到JProfiler之后經(jīng)過解析,默認會跳到該界面,這里直接選上面的最大對象,繼續(xù)解析。

這里右鍵選定比較大的對象后會彈出這樣一個框,選擇引用-傳入引用。為啥是傳入引用呢,因為我們要找問題的源頭啊,哪里來的才是比較重要的。

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

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

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

如何解決CPU占用高問題
CPU占用高的問題就沒有掛了之后自動dump文件的好事了。這時候需要善用jstack、監(jiān)控和Arthas等工具。
發(fā)現(xiàn)
正常來說,咱們會有監(jiān)控軟件去監(jiān)控服務(wù)器的一些性能指標,我這用的是Prometheus+Grafana,非常大眾哈。

如圖可以觀察到一個服務(wù)器CPU占用的折線圖,配合告警可以及時通知相關(guān)人員定位問題。
定位-傳統(tǒng)武學
通過上面地監(jiān)控及時發(fā)現(xiàn)問題,接下來就該上手具體的操作了。
- top -o %CPU,Linux上按CPU從大到小排序,找到占用最多的PID(這里假設(shè)是Java應(yīng)用)
- jstack pid > thread.txt,通過jstack命令打印當前Java應(yīng)用的堆棧信息
- top -Hp pid,通過該命令觀察此pid進程中所有線程的CPU占用
- 找到線程pid,通過命令printf '%x\n' pid得到轉(zhuǎn)換為16進制的nid
- 在jstack獲得的文件thread.txt中,找到nid對應(yīng)的線程堆棧信息,找到對應(yīng)代碼塊即可
- 通常除了CPU占用過高的線程,還需要重點關(guān)注線程狀態(tài)為BLOCKED、WAITING和TIMED_WAITING的部分
定位-新派寶典
我一開始接觸的也是傳統(tǒng)武學,啪啪啪一堆命令敲得也是非常麻煩嗷,那有沒有開箱即用的好東西呢。沒錯,那肯定是有的,就是大名鼎鼎的Arthas啦。
- 下載Arthas.jar,curl -O arthas.aliyun.com/arthas-boot…
- 運行java -jar arthas-boot.jar并選擇需要監(jiān)聽的Java應(yīng)用,圖形化很贊
- 輸入命令dashboard打開看板,隨時監(jiān)控,默認5000ms一刷
- 針對上面CPU問題,直接選擇Thread系列命令
效果如下,牛中牛中牛,解放雙手。相比jstack輸出的文件,甚至多了cpuUsage這個參數(shù),更加直觀。

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