search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

常見Java故障案例

JAVA(3)

目錄

  • HotSpot常識

  • Java故障排查方法論

  • Java故障案例分析

HotSpot是目前最常見的開源JVM(GPL協議),用來運行Java應用和applet,本次討論基本都是基於這一軟體來進行的。

所有的Java對象都是分配在Java堆上的,Java代碼中看到的引用,在JVM的實現中就是一個指針,指向一段被表示成對象的內存區域。這個區域可能被移動,引用指針的值不同於一般的C/C++指針,是會從外部改變的。

執行的Java位元組碼都是動態載入、鏈接、編譯的。

JIT compiler,JVM裡面有一個模塊負責把Java位元組碼編譯成優化過的native機器碼,這樣可以極大提高執行效率。

但 是HotSpot的JIT編譯器只會編譯熱點方法,一個Java方法load進來後會默認從解釋器開始執行,只有部分或整體的解釋執行次數超過一定次數才 會被編譯優化,在某些條件下,比如debug,會把方法去優化退回到解釋器執行。解釋器可以看做是一個沒有優化的翻譯器,會把每一條bytecode指令 機械的翻譯成彙編指令來執行。

1.6, 兩個stack,interned string放到heap

這張圖裡每一個小方塊展開都可以寫一系列文章,今天就不在這裡展開了。

參考鏈接:

Part 2

Java故障排查方法論

1 1參考書

2 幾個我個人常用的三個原則
  • 從淺顯和廣泛開始。分析問題應該盡量從高層入手,收集各種各樣的現場信息,版本信息,盡量不要一開始就debugger跑起。

  • 分而治之,隔離問題。將問題隔離到儘可能小的領域中,比如某個特定系統、特定版本、 甚至特定機器中。之後如果是java的問題,還可以繼續分析是java應用、容器、或者jdk的問題,最後應該能確定到某個模塊的某些代碼、一次 commit、一行配置的問題。整個排查問題的過程就是一個從上到下,一步步縮小問題範圍的過程。

  • 福爾摩斯法則。當排除了所有的不可能,那麼剩下的那個,不管多麼荒謬,就是罪魁禍首了。

3 重現故障和收集數據

不同於其他工業系統,軟體工業的一個好處就是重新嘗試的代價一般都特別小,重啟一個進程總比重啟一台發動機、一個核反應堆輕鬆很多。所以如果故障問題能穩定的通過重啟復現,這對於修bug的同學將會是個天大的好消息。

但是現實中,特別是在生產環境中,更多的事後故障問題不是你想發現就能發現,經常是重啟后就沒了,跑了不確定的時間就又出現了,所以只能通過收 集故障時的系統狀態數據來分析問題。狀態數據大致可以分為兩類:一是監控類數據,收集這類數據對於應用的性能影響很小,基本可以忽略不計,所以可以持續收 集,比如GC log,應用log等;第二類是某些瞬時數據,這些數據要麼收集的代價很大,很影響系統性能,要麼時效性很高,過了故障點一切可能就都不一樣了,所以不能 持續收集,必須迅速的在故障出現點自動採集,比如Heap dump,core dump等。

下面這個圖描述了常見的Java故障和需要收集的數據之間的概要關係

JVM級別數據

對於JVM,下面這些選項最好常年打開選項,對於收集故障數據很有幫助

系統級別數據

Java進程運行的環境信息也是重要的診斷信息,如果能在故障點全部收集下來對於後續調試分析也是很有幫助的,這些信息主要包括: 系統基本軟硬體信息、所有進程的情況、打開的文件描述符等等。

簡單的做法可以在Java進程非正常返回的時候執行一個腳本,自動的去採集一遍這些信息。(HotSpot支持在致命錯誤或者oom時執行一個系 統命令,可以設置讓其去直接執行這個腳本)。或者說是使用一個監控程序,監視Java進程的輸出結果,如果發現異常、crash等情況,就收集一次環境信 息。

Part 3

Java故障案例分析

故障1CPU load過高

問題一般是指CPU使用率很高,但是系統並沒有很繁忙,一般有兩種情形。

情況1,啟動階段

應用剛啟動之後或者剛放了用戶流量之後,也是可能突然cpu load飆到很高的,這一般不是java代碼引起的,而是由於jvm的jit編譯器引起的。(當然如果你使用的是一些非普遍的JDK,比如IBMJDK, 並且啟用了AOT之類的功能,是不可能遇到這個情況的,因為代碼已經提前編譯好了)

  • -XX:+TieredCompilation

可以先一定程度上減輕這個問題,效果上相當於把消耗資源嚴重的一些優化處理延後進行了,先把java方法編譯到一個低優化級別的native方 法。值得注意的是,這個參數會消耗比較多的內存資源,同一個方法被編譯了多次,存在多份native內存拷貝,建議是把codecache調大一點兒 (-XX:+ReservedCodeCacheSize,InitialCodeCacheSize)。

Optional:

CodeCache不足可能會引起性能問題,這是一種非常少見的故障,code cache不足,jit需要編譯新的方法的時候就會不停的嘗試清理code cache,丟棄掉無用的方法,頻繁的嘗試會導致大量資源消耗在JIT線程上。

  • -XX:+PrintCompilation

為了確認這個問題可以嘗試使用這個參數,輸出JIT編譯的情況,如果初始階段發生大量方法的編譯,就可以確定是由於JIT編譯引起的。一般情況下,忍一忍熬過一開始的編譯階段就好了。如果用戶請求超時嚴重,無法忍受,可以嘗試使用分層編譯、提前預熱系統。

情況2,非啟動階段

一般是一些計算密集型任務、忙等操作、或者過於密集的線程調度。一般需要定位出被頻繁執行的代碼邏輯(熱點方法),然後再進行優化,目前可以使用 各種profile工具來分析。比如Java Mission Control, ZProfiler(硬廣:阿里自產的profiler工具)

這個問題又兩個層面,一個是應用的性能下降了,這一般是來自監控系統或者用戶突然的報警 。從分割問題的角度看,性能下降一般是和之前時間點比較得出的結論,那麼就肯定有一個分水嶺,在某一個時間點(通常是一個改動發生的時候)之後就會開始性 能下降。所以初始的解決方案比較簡單,就是找到改動發生的時間點,挑出造成性能下降的改動,然後分析這個改動為什麼會造成性能下降。

但是如果就是一個應用性能較差的問題,就比較棘手了,這個通常意味著沒有可以比較的時間點,相當於憑空設定一個性能指標,將系統性能優化提升到這 個目標。通常這是一個需要多方合作,修改多個層次的代碼、配置才能達到的目標。通常而言可以繼續嘗試profiling Java應用,分析性能瓶頸,優化瓶頸部分。

可能有影響的瓶頸包括:

這個一般需要設計、代碼層面的改動,使用更高效的加鎖機制,減輕競爭,等等。

GC

頻繁full gc的又有兩種情況,一種是說full gc完了之後整個heap還是沒有很多的可用空間,一般是可能是由於最大heap上限可能設置有點兒小了,或者應用有內存泄露,需要做個heap dump具體分析下內存裡面各個部分的使用情況。

另外一個情況是full gc完了之後整個heap還是有不少的可用空間的,比如下圖,這個一般是有一些「臨時」對象晉陞到了老年代,新生代沒有濾掉足夠的短生命周期對象,可能需 要調整JVM參數-XX:MaxTenuringThreshold(15, 4bits)提高promote到老年代的門檻。

分析GC日誌,一個開源的免費解決方案是eclipse的GCMV

GC參數優化

關於GC其實你能做的並不多,影響最大就是通過調整JVM啟動時參數,來調節GC的各個行為,但是推薦讀懂了官方文檔中的說明再做調整:

1. 仔細設計一個適合你自己環境、應用的參數模板。

2. 收集應用信息,評估應用內存活動行為(參見「Java性能優化權威指南」),常駐內存對象大小,大對象比例,native內存使用,分配速度等。。。

3. 調整下列參數(不是一條命令哦)

-Xms8888m

-Xmx8888m

-Xmn8888m

-Xss8888k

-XX:PermSize=8888m

-XX:MaxPermSize=8888m

-XX:+UseStringCache

-XX:+UseConcMarkSweepGC

-XX:+UseG1GC

-XX:+UseParNewGC

-XX:ParallelGCThreads=8888

-XX:+CMSClassUnloadingEnabled

-XX:+DisableExplicitGC

-XX:+UseCMSInitiatingOccupancyOnly

-XX:CMSInitiatingOccupancyFraction=88

GC停頓時間太長

堆太大的時候,CMS GC可能會停頓比較久的時間,-XX:+CMSScavengeBeforeRemark能通過在remark階段前做一次young gc減輕這個時間。

另外可以考慮換G1。

故障3 內存耗盡OOM

基本的解決思路,多給點兒或者少用點兒唄。

  • Java對象真的耗盡了內存資源,Eclipse MAT

    HeapDump,分析內存泄露,大對象,對象關係圖。

  • Native內存耗盡, DirectBuffer,malloc

    JVM運行過程中,雖然會對Java堆做垃圾收集,但是如果jni或者非DirectBuffer的Unsafe分配的內存沒有回收,會逐漸累積直至java進程結束。DirectBuffer雖然Java對象很小,但是使用的內存可能會很多。

    參考:http://lovestblog.cn/blog/2015/05/12/direct-buffer/

  • PermGen耗盡,一般動態類載入導致,已經成為歷史,儘早升級吧。

故障4 崩潰crash

現代JVM發展到今天已經很健壯了,一般很少會出現crash的情況,如果出現了,很有可能是Java代碼執行了不安全的操作,比如使用Unsafe去直接操作內存、自己編寫了JNI函數中crash了。

目前的現實是很多第三方的庫確實直接使用了Unsafe去實現各種「高效」的操作,隨便搜索下Github就可以看到大量的開源Java、Scala庫使用了JDK提供的unsafe類

對於crash的情形,需要收集的信息包括各種dump,最關鍵的是系統core dump,方便將來使用GDB做事後分析,在linux上一般需要使用ulimit –c unlimited 命令修改core文件尺寸上限才行。

有了core dump,剩下的分析一般都是使用GDB繼續了,crash的情形一般反而比較直觀。如果不是unsafe、自己jni引起的crash問題,恭喜你,真的發現bug了,這個問題直接給Oracle或者java社區報bug吧。

腳本太複雜,怎麼知道最後跑起來的Java進程到底設置了哪些參數?

-XX:-PrintCommandLineFlags



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦