聽阿里巴巴JVM工程師爲你分析常見Java故障案例

聽阿里巴巴JVM工程師爲你分析常見Java故障案例

\

本文根據12月23日阿里巴巴技術保障部JVM組軟件工程師陸傳勝老師在【DBA+社羣,微信公衆號:dbaplus】的主題分享整理!小編特別整理出其中精華內容,供你們學習交流。同時,也很是感謝陸傳勝老師對DBA+社羣給予的大力支持。 html

 

目錄 java

  • HotSpot常識 linux

  • Java故障排查方法論 c++

  • Java故障案例分析 安全

Part 1 性能優化

   

HotSpot常識 微信

 

 

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

     

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

     

  • 執行的Java字節碼都是動態加載、連接、編譯的。 eclipse

     

  • JIT compiler,JVM裏面有一個模塊負責把Java字節碼編譯成優化過的native機器碼,這樣能夠極大提升執行效率。

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

 

1.6, 兩個stack,interned string放到heap

 

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

 

參考連接:

http://blog.jamesdbloom.com/images_2013_11_17_17_56/JVM_Internal_Architecture.png

http://blog.jamesdbloom.com/JVMInternals.html

 

Part 2

   

Java故障排查方法論

 

 

 

11參考書

 

 

   

 

  

 

 

2幾個我我的經常使用的三個原則

 

 

  • 從淺顯和普遍開始。
    分析問題應該儘可能從高層入手,收集各類各樣的現場信息,版本信息,儘可能不要一開始就debugger跑起。

     

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

     

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

 

 

3重現故障和收集數據

 

 

不一樣於其餘工業系統,軟件工業的一個好處就是從新嘗試的代價通常都特別小,重啓一個進程總比重啓一臺發動機、一個核反應堆輕鬆不少。因此若是故障問題能穩定的經過重啓復現,這對於修bug的同窗將會是個天大的好消息。

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

 

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

 

 

JVM級別數據

 

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

 

-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/log/gcdump

 

系統級別數據

 

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工具)

 

 

故障2應用性能降低/較差

 

 

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

 

但 是若是就是一個應用性能較差的問題,就比較棘手了,這個一般意味着沒有能夠比較的時間點,至關於憑空設定一個性能指標,將系統性能優化提高到這個目標。通 常這是一個須要多方合做,修改多個層次的代碼、配置才能達到的目標。一般而言能夠繼續嘗試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的各個行爲,可是推薦讀懂了官方文檔中的說明再作調整:
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

 

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

 

 

 

Q & A  

 

 

Q1:關於內存泄露,是否是能夠用一些開源產品來加探針,這方面有沒有好的建議?

A1:內存泄露其實有兩個方面,若是是native的malloc泄露,能夠經過 valgrind,jemalloc等工具發現,這個和傳統c++應用內存同樣的;對於某些由於強引用鏈沒法釋放的java對象,通常都是 heapdump以後使用eclipse的mat工具分析「可能泄露的」,由於程序並無結束,並且還持有強引用,很可貴出結論說某些對象是否是內存泄露。

 

Q2:關於內存泄露, 怎麼定位到具體代碼?

A2:定位到代碼目前比較可行的,就是用MAT分析出那些類型的對象發生了泄露,而後去掃描代碼看看這些對象在哪裏分配的,固然若是大量的string,byte.就很差辦了,通常是以用戶應用自定義的類爲線索去查找。這樣能夠大大下降工做量。

 

講師介紹:陸傳勝

  • 現就任於阿里巴巴技術保障部JVM組,主要工做是阿里巴巴定製化JDK的開發, 以及相關的Java技術支持。

  • 曾就任於IBM Java技術中心,負責IBMJDK開發、參與OpenJDK社區,是OpenJDK jdk8項目committer。

  • 聯繫方式chuanshenglu@gmail.com

相關文章
相關標籤/搜索