<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實(shí)戰(zhàn)案例分享:根據(jù) JVM crash 日志定位和分析問題

          共 6680字,需瀏覽 14分鐘

           ·

          2020-10-28 22:47

          點(diǎn)擊上方藍(lán)色字體,選擇“設(shè)為星標(biāo)

          回復(fù)”資源“獲取更多資源

          大數(shù)據(jù)技術(shù)與架構(gòu)
          點(diǎn)擊右側(cè)關(guān)注,大數(shù)據(jù)開發(fā)領(lǐng)域最強(qiáng)公眾號(hào)!

          大數(shù)據(jù)真好玩
          點(diǎn)擊右側(cè)關(guān)注,大數(shù)據(jù)真好玩!

          1. JVM crash了

          下面是一份crash report, 下面是截取了crash report的部分,用于分析:

          # Problematic frame:
          # V [libjvm.so+0x5bbf05] instanceKlass::oop_follow_contents(ParCompactionManager*, oopDesc*)+0x2c5
          Stack 信息:
          Stack: [0x00007fa9482b3000,0x00007fa9483b4000], sp=0x00007fa9483b2a10, free space=1022k
          Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
          V [libjvm.so+0x5bbf05] instanceKlass::oop_follow_contents(ParCompactionManager*, oopDesc*)+0x2c5
          V [libjvm.so+0x87504c] ParCompactionManager::follow_marking_stacks()+0x1ec
          V [libjvm.so+0x85c138] MarkFromRootsTask::do_it(GCTaskManager*, unsigned int)+0x78
          V [libjvm.so+0x55813f] GCTaskThread::run()+0x12f
          V [libjvm.so+0x821ca8] java_start(Thread*)+0x108
          • 看到里面的棧信息是GCTaskThread線程,初步判斷在執(zhí)行GC的時(shí)候發(fā)生了crash,代碼段在0x5bbf05,函數(shù)是instanceKlass::oop_follow_content。

          • InstanceKlass 就是我們常說的class對象,因?yàn)槭窃贕C的時(shí)候出現(xiàn)問題,具體的代碼段通常是在GC部分并不能容易的判斷發(fā)生了什么,而我們更需要知道的是GC的時(shí)候在處理哪個(gè)對象出了問題

          2. GC 的參數(shù)

          JVM在GC的控制參數(shù)中,有一個(gè)GC前進(jìn)行校驗(yàn)的參數(shù),在校驗(yàn)過程中當(dāng)發(fā)生地址異常的化會(huì)打印出異常的地址,并且讓JVM crash,因?yàn)檫@個(gè)參數(shù)每一次GC都要檢查,包括新生代的GC,影響一定的性能,并不適合在產(chǎn)品環(huán)境中使用,但對發(fā)現(xiàn)GC中的對象問題,卻非常有幫助。

          -XX:+VerifyBeforeGC -XX:+VerifyAfterGC

          產(chǎn)品的日志打印出了異常的對象地址:

          Failed: 0x000000079ac5fe30 -> 0x0000000410bc55c0

          3. SA 工具之CLHSDB

          知道錯(cuò)誤的對象地址,需要分析core dump知道哪個(gè)對象出了問題,在Linux上通常會(huì)用GDB,但是這并不適合分析我們初學(xué)者,尤其是我們并不是非常清楚對象的結(jié)構(gòu)和布局,我們需要利用JMV提供的SA工具 JVM提供的HSDB工具是一款非常好的工具,通過工具能查看和分析運(yùn)行中的JVM的heap對象,當(dāng)然也可以常看core dump, 但問題是HSDB是有UI界面的,我們在linux系統(tǒng)中通常沒有UI界面,用過HSDB工具,可以發(fā)現(xiàn)當(dāng)我們啟動(dòng)命令控制臺(tái)的時(shí)候,實(shí)際上HSDB是把CLHSDB嵌入在了HSDB的圖形界面里,那我們可以使用CLHSDB來通過命令行的方式進(jìn)行dump分析。

          3.1 如何啟動(dòng)CLHSDB

          java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB

          Attach 一個(gè)core dump:

          java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB $JAVA_HOME/bin/java 99083

          這里有幾個(gè)注意點(diǎn):

          • 版本問題,如果產(chǎn)品上裝了多個(gè)JVM環(huán)境的化,注意core dump要和JVM的分析的版本一致

          • SA環(huán)境需要root權(quán)限

          3.2 分析對象

          在前面提到的日志中,錯(cuò)誤的對象地址是:Failed: 0x000000079ac5fe30 -> 0x0000000410bc55c0

          先掃描一下0x000000079ac5fe30附近的地址的對象

          可以看到0x000000079ac5fe30地址最近的對象的地址0x000000079ac5fe08這是一個(gè)MemberName對象,繼續(xù)查看地址0x000000079ac5fe30的內(nèi)容

          查看一下地址0x0000000782178ab8的對象,就是一個(gè)method的對象

          這樣我們就能構(gòu)建了地址的 0x000000079ac5fe30對象

          • 地址0x000000079ac5fe30 是屬于0x000000079ac5fe08地址的對象的成員,也就是MemberName對象的成員

          • 通過0x0000000782178ab8的地址分析,這是一個(gè)reinvokeTarget的method的地址

          我們在來看MemberName的對象結(jié)構(gòu)

           final class More ...MemberName implements Member, Cloneable {
          73 private Class clazz; // class in which the method is defined
          74 private String name; // may be null if not yet materialized
          75 private Object type; // may be null if not yet materialized
          76 private int flags; // modifier bits; see reflect.Modifier
          77 //@Injected JVM_Method* vmtarget;
          78 //@Injected int vmindex;
          79 private Object resolution; // if null, this guy is resolved
          }

          無論從0x0000000782178ab8的地址對象反向分析,還是從0x000000079ac5fe08地址位移分析,我們都可以很準(zhǔn)確的判定,0x000000079ac5fe30對應(yīng)的是vmtarget的對象。(在JVM里經(jīng)常會(huì)內(nèi)部修改一些類的內(nèi)部結(jié)構(gòu)用于記錄狀態(tài),但是又不能被Java應(yīng)用修改)

          但是有點(diǎn)不對,剛才不是地址是 0x0000000410bc55c0,怎么現(xiàn)在變成了0x0000000782178ab8? 要知道這兩個(gè)地址為何不一樣,我們先要對應(yīng)代碼段,地址 0x0000000410bc55c0是怎么獲取到的?Crash report里會(huì)有堆棧信息 crash report就不貼了,最后調(diào)用的是VerifyFieldColsure:do_oop

          class VerifyFieldClosure: public OopClosure {
          protected:
          template void do_oop_work(T* p) {
          guarantee(Universe::heap()->is_in_closed_subset(p), "should be in heap");
          oop obj = oopDesc::load_decode_heap_oop(p);
          if (!obj->is_oop_or_null()) {
          tty->print_cr("Failed: " PTR_FORMAT " -> " PTR_FORMAT, p, (address)obj);
          Universe::print();
          guarantee(false, "boom");
          }
          }
          public:
          virtual void do_oop(oop* p) { VerifyFieldClosure::do_oop_work(p); }
          virtual void do_oop(narrowOop* p) { VerifyFieldClosure::do_oop_work(p); }
          };

          日志里打印的

          Failed: 0x000000079ac5fe30 -> 0x0000000410bc55c0

          就是這個(gè)函數(shù)打印出來的,在代碼里obj的地址很明顯的調(diào)用了函數(shù)load_decode_heap_oop(p)

          inline oop oopDesc::load_decode_heap_oop_not_null(oop* p)       { return *p; }
          inline oop oopDesc::load_decode_heap_oop_not_null(narrowOop* p) {
          return decode_heap_oop_not_null(*p);
          }

          在oop和narrowOop的情況下是不一樣的獲取地址方式

          3. 指針的壓縮

          在繼續(xù)分析下去之前,我們先要介紹oop, narrowOop的背景

          在JVM 1.6后面為了節(jié)省heap的堆內(nèi)存會(huì)使用壓縮指針地址的設(shè)計(jì),因?yàn)閷ο蠼Y(jié)構(gòu)里指向別的對象是指針引用oop,這個(gè)地址是保存在Heap中的,保存Bit 64的地址太浪費(fèi)Heap空間,所以JVM里保存了一個(gè)以heap的基地址為基本地址,計(jì)算對象真實(shí)地址和基本地址差值并且通過位移(shift)來節(jié)省空間,該指針定義為narrow_oop而不同于常見的oop 一個(gè)小坑:雖然使用了narrow_oop,當(dāng)指定的heap的地址空間低于一個(gè)閥值的情況下會(huì)將narrow_oop的基地址和shift都設(shè)置為0,也就是不壓縮指針可以通過設(shè)置參數(shù):-XX:+PrintCompressedOopsMode 打印來判斷narrowoop的base和shift

          0x0000000410bc55c0 是個(gè)無效地址,而0x0000000782178ab8卻是個(gè)有效地址,對應(yīng)的是method instance同時(shí)也能匹配上MemberName.vmtarget,我們可以認(rèn)為0x0000000782178ab8的地址是有效的,為何JVM通過decode地址是0x0000000410bc55c0確實(shí)個(gè)無效地址,非常有可能存在JVM并沒有把壓縮后的地址保存在vmtarget中,而是直接把真實(shí)的地址賦給了vmtarget,為了猜測是否有效,我們來看jvm的代碼

          void java_lang_invoke_MemberName::adjust_vmtarget(oop mname, oop ref) {
          mname->address_field_put(_vmtarget_offset, (address)ref);
          }

          4. MethodHandler

          雖然我們找到了JVM crash問題的根因,但我們還需要繼續(xù)深入的找到誰才是罪魁禍?zhǔn)祝褪荍VM為何會(huì)調(diào)整vmtarget的值 分析誰調(diào)用了adjust_vmtarget函數(shù)即可

           void MemberNameTable::adjust_method_entries(methodOop* old_methods, methodOop* new_methods,
          int methods_length, bool *trace_name_printed) {
          assert(SafepointSynchronize::is_at_safepoint(), "only called at safepoint");
          - // search the MemberNameTable for uses of either obsolete or EMCP methods
          + // For each redefined method
          for (int j = 0; j < methods_length; j++) {
          methodOop old_method = old_methods[j];
          methodOop new_method = new_methods[j];
          - oop mem_name = find_member_name_by_method(old_method);
          - if (mem_name != NULL) {
          - java_lang_invoke_MemberName::adjust_vmtarget(mem_name, new_method);
          -
          - if (RC_TRACE_IN_RANGE(0x00100000, 0x00400000)) {
          - if (!(*trace_name_printed)) {
          - // RC_TRACE_MESG macro has an embedded ResourceMark
          - RC_TRACE_MESG(("adjust: name=%s",
          - Klass::cast(old_method->method_holder())->external_name()));
          - *trace_name_printed = true;
          - }
          - // RC_TRACE macro has an embedded ResourceMark
          - RC_TRACE(0x00400000, ("MemberName method update: %s(%s)",
          - new_method->name()->as_C_string(),
          - new_method->signature()->as_C_string()));
          - }

          很幸運(yùn),只有methodhandles.cpp調(diào)用,而函數(shù)adjust_method_entries,只在redefineclass的時(shí)候調(diào)用就是在instrument的時(shí)候,目前比較紅火的RASP技術(shù)的核心關(guān)鍵。

          5. 如何修復(fù)?

          既然問題出現(xiàn)在地址壓縮上,那么修復(fù)就變的非常簡單,只要壓縮地址后保存就可以了

          mname->address_field_put(_vmtarget_offset, (address)ref);

          改成

          mname->obj_field_put(_vmtarget_offset, new_method);

          如果你不想修改代碼?

          • 一種方法比較簡單,就是instrument的時(shí)候不修改methodhandle的類就好

          • 既然問題出在壓縮指針上,不壓縮不就沒問題了么?JVM提供了環(huán)境參數(shù)可以控制是否壓縮指針

           -XX:+UseCompressedOops

          這樣一個(gè)完成的通過JVM crash 日志和core dump進(jìn)行JVM的問題定位和分析結(jié)束了,希望能對你有所幫助。

          版權(quán)聲明:

          本文為大數(shù)據(jù)技術(shù)與架構(gòu)整理,原作者獨(dú)家授權(quán)。未經(jīng)原作者允許轉(zhuǎn)載追究侵權(quán)責(zé)任。
          編輯|冷眼丶
          微信公眾號(hào)|import_bigdata


          歡迎點(diǎn)贊+收藏+轉(zhuǎn)發(fā)朋友圈素質(zhì)三連


          文章不錯(cuò)?點(diǎn)個(gè)【在看】吧!??

          瀏覽 56
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  a√在线看 | 操逼大秀 | 骚逼五月婷婷影院 | 人人妻人人澡欧美91精品 | 久久精品国产精品 |