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

jopen 9年前發布 | 48K 次閱讀 JVM

本文根據12月23日阿里巴巴技術保障部JVM組軟件工程師陸傳勝老師在【DBA+社群,微信公眾號:dbaplus】的主題分享整理!小編特別整理出其中精華內容,供大家學習交流。同時,也非常感謝 陸傳勝

老師對DBA+社群給予的大力支持。

目錄

  • HotSpot常識

    </li>

  • Java故障排查方法論

    </li>

  • Java故障案例分析

    </li> </ul>

    Part 1

    HotSpot常識

    </div> </div>

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

      </li>

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

      </li>

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

      </li>

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

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

      </li> </ul>

      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故障排查方法論

      </div> </div>

      1 1參考書

      </div>

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

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

      2 幾個我個人常用的三個原則

      </div>

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

        </li>

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

        </li>

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

        </li> </ul>

        3 重現故障和收集數據

        </div>

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

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

        </div>

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

        JVM級別數據

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

        -XX:+PrintGCDetails

        -XX:+PrintGCDateStamps

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

        </div> </div>

        系統級別數據

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

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

        Part 3

        Java故障案例分析

        </div> 故障1 CPU load過高 </div>

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

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

        情況1,啟動階段

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

        • -XX:+TieredCompilation

          </li> </ul>

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

          Optional:

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

          • -XX:+PrintCompilation

            </li> </ul>

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

            情況2,非啟動階段

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

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

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

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

            </div>

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

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

            可能有影響的瓶頸包括:

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

            GC

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

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

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

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

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

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

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

            GC參數優化

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

            http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

            </span></div> </div>

            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

            </span></div>

            GC停頓時間太長

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

            另外可以考慮換G1。

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

            故障3 內存耗盡OOM

            </div>

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

            • Java對象真的耗盡了內存資源,Eclipse MAT
              HeapDump,分析內存泄露,大對象,對象關系圖。

              </li>

            • Native內存耗盡, DirectBuffer,malloc

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

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

              </li>

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

              </li> </ul>

              故障4 崩潰crash

              </div>

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

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

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

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

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

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

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

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

              -XX:-PrintCommandLineFlags

              Q & A
              </div> </div>

              Q1: 關于內存泄露,是不是可以用一些開源產品來加探針,這方面有沒有好的建議?

              A1: 內存泄露其實有兩個方面,如果是native的malloc泄露,可以通過 valgrind,jemalloc等工具發現,這個和傳統c++應用內存一樣的;對于某些因為強引用鏈無法釋放的java對象,一般都是 heapdump之后使用eclipse的mat工具分析“可能泄露的”,因為程序并沒有結束,而且還持有強引用,很難得出結論說某些對象是不是內存泄 露。

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

              A2: 定位到代碼目前比較可行的,就是用MAT分析出那些類型的對象發生了泄露,然后去掃描代碼看看這些對象在哪里分配的,當然如果大量的string,byte.就不好辦了,一般是以用戶應用自定義的類為線索去查找。這樣可以大大降低工作量。

              講師介紹:陸傳勝

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

                </li>

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

                </li> </ul> 原文 http://dbaplus.cn/news-21-173-1.html

                 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
                 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
                 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!