<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                [TOC] # 死鎖的四大條件 (1)?互斥條件:一個資源每次只能被一個進程使用。 (2)?請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。 (3)?不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。 (4)?循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系 # 死鎖的監測 方案1 (利用生成系統的traces文件) 定期打印全部現場堆棧 對HandleThread的監控(類似Looper 裝炸彈/拆炸彈/引爆) ## 死鎖分析 https://blog.csdn.net/Tencent\_Bugly/article/details/79757867 Android在發生ANR時有一套系統機制: 1、Android應用發生ANR時,系統會發出SIGQUIT信號給發生ANR進程。 2、系統信號捕捉線程觸發輸出/data/anr/traces.txt文件,記錄問題產生虛擬機、線程堆棧相關信息。 3、這個trace文件中包含了線程信息和鎖的信息,借助這個trace文件可以分析卡死的原因。 由此,如果利用這個系統原有的機制,自己在線程卡死時候觸發traces文件的形成進行上報,便可以把線程卡死的關鍵進行進行上報。本監控方案便是利用系統機制進行卡死信息的抓取: 1、當監控線程發現被監控線程卡死時,主動向系統發送SIGQUIT信號。 2、等待/data/anr/traces.txt文件生成。 3、文件生成以后進行上報。 # 死鎖監控 方案2 在發生ANR的時候,有時候只有主線程堆棧信息可能還不夠,例如發生死鎖的情況,**需要知道當前線程在等待哪個鎖,以及這個鎖被哪個線程持有**,然后把發生死鎖的線程堆棧信息都收集到。 流程如下: 1. 獲取當前blocked狀態的線程 2. 獲取該線程想要競爭的鎖 3. 獲取該鎖被哪個線程持有 4. 通過關系鏈,判斷死鎖的線程,輸出堆棧信息 在Java層并沒有相關API可以實現死鎖監控,可以從Native層入手。 ## 獲取當前blocked狀態的線程 這個比較簡單,一個for循環就搞定,不過我們要的線程id是native層的線程id,Thread 內部有一個native線程地址的字段叫 `nativePeer`,通過反射可以獲取到。 ~~~ Thread[] threads = getAllThreads(); for (Thread thread : threads) { if (thread.getState() == Thread.State.BLOCKED) { long threadAddress = (long) ReflectUtil.getField(thread, "nativePeer"); // 找不到地址,或者線程已經掛了,此時獲取到的可能是0和-1 if (threadAddress <= 0) { continue; } ...后續 } } ~~~ 有了native層線程地址,還需要找到native層相關函數 ## 獲取當前線程想要競爭的鎖 從ART 源碼可以找到這個函數 [androidxref.com/8.0.0\_r4/xr…](https://link.juejin.cn?target=http%3A%2F%2Fandroidxref.com%2F8.0.0_r4%2Fxref%2Fart%2Fruntime%2Fmonitor.cc "http://androidxref.com/8.0.0_r4/xref/art/runtime/monitor.cc") 函數:**Monitor::GetContendedMonitor** ![](https://img.kancloud.cn/04/29/0429ae3a73c0bff44f8ba6d84a867ced_1240x586.png) 從源碼和源碼的解釋可以看出,這個函數是用來獲取當前線程等待的Monitor。 順便說說Monitor以及Java對象結構 #### Monitor **Monitor是一種并發控制機制**,提供多線程環境下的互斥和同步,以支持安全的并發訪問。 Monitor由以下3個元素組成: 1. 臨界區:例如synchronize修飾的代碼塊 2. 條件變量:用來維護因不滿足條件而阻塞的線程隊列 3. Monitor對象,維護Monitor的入口、臨界區互斥量(即鎖)、臨界區和條件變量,以及條件變量上的阻塞和喚醒 感興趣可以參考這一篇文章 [說一說管程(Monitor)及其在Java synchronized機制中的體現](https://link.juejin.cn?target=https%3A%2F%2Fwww.jianshu.com%2Fp%2Fe624460c645c "https://www.jianshu.com/p/e624460c645c") #### Java的Class對象 Java的Class對象包括三部分組成: 1. 對象頭:MarkWord和對象指針 > **MarkWord(標記字段)**:保存哈希碼、分代年齡、**鎖標志位**、偏向線程ID、偏向時間戳等信息 **對象指針**:即指向當前對象的類的元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。 2. 實例數據:對象實際的數據 3. 對齊填充:按8字節對齊(JVM自動內存管理系統要求對象起始地址必須是8字節的整數倍)。例如Integer對象,對象頭MarkWord和對象指針分別占用4字節,實例數據4字節,那么對齊填充就是4字節,Integer占用內存是int的4倍。 * * * 回到 `GetContendedMonitor` 函數,我們可以通過打開動態庫`libart.so`,然后使用`dlsym`獲取函數的符號地址,然后就可以進行調用了。 由于Android 7.0開始,系統限制App中調用`dlopen`,`dlsym`等函數打開系統動態庫,我們可以使用 [ndk\_dlopen](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Frrrfff%2Fndk_dlopen "https://github.com/rrrfff/ndk_dlopen")這個庫來繞過這個限制 ~~~ //1、初始化 ndk_init(env); //2、打開動態庫libart.so void *so_addr = ndk_dlopen("libart.so", RTLD_NOLOAD); if (so_addr == NULL) { return 1; } ~~~ 打開動態庫之后,會返回動態庫的內存地址,接下來就可以通過`dlsym`獲取`GetContendedMonitor`這個函數的符號地址,只不過要注意,c++可以重載,所以它的函數符號比較特殊,需要從`libart.so`中搜索匹配找到 ~~~ //c++跟c不一樣,c++可以重載,描述符會變,需要打開libart.so,在里面搜索查找GetContendedMonitor的函數符號 //http://androidxref.com/8.0.0_r4/xref/system/core/libbacktrace/testdata/arm/libart.so //獲取Monitor::GetContendedMonitor函數符號地址 get_contended_monitor = ndk_dlsym(so_addr, "_ZN3art7Monitor19GetContendedMonitorEPNS_6ThreadE"); if (get_contended_monitor == NULL) { return 2; } 復制代碼 ~~~ 到此,第一個函數的符號地址找到了,接下來要找另外一個函數 ## 獲取目標鎖被哪個線程持有 **函數:Monitor::GetLockOwnerThreadId** ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/668ca9bf80d642169a6bce7cf3c45ed7~tplv-k3u1fbpfcp-watermark.awebp) 用同樣的方式來獲取這個函數符號地址 ~~~ // Monitor::GetLockOwnerThreadId //這個函數是用來獲取 Monitor的持有者,會返回線程id get_lock_owner_thread = ndk_dlsym(so_addr, get_lock_owner_symbol_name(api_level)); if (get_lock_owner_thread == NULL) { return 3; } 復制代碼 ~~~ 由于從android 10開始,這個`GetLockOwnerThreadId`函數符號有變化,所以需要通過api版本來判斷使用哪一個 ~~~ const char *get_lock_owner_symbol_name(jint level) { if (level <= 29) { //android 9.0 之前 //http://androidxref.com/9.0.0_r3/xref/system/core/libbacktrace/testdata/arm/libart.so 搜索 GetLockOwnerThreadId return "_ZN3art7Monitor20GetLockOwnerThreadIdEPNS_6mirror6ObjectE"; } else { //android 10.0 // todo 10.0 源碼中這個函數符號變了,需要自行查閱 return "_ZN3art7Monitor20GetLockOwnerThreadIdEPNS_6mirror6ObjectE"; } } ~~~ 到此,就得到了兩個函數符號地址,接下來就把blocked狀態的native線程id傳過去,調用就行了 ## 找到一直不釋放鎖的線程 ~~~ Java_com_lanshifu_demo_anrmonitor_DeadLockMonitor_getContentThreadIdArt(JNIEnv *env,jobject thiz,jlong native_thread) { LOGI("getContentThreadIdArt"); int monitor_thread_id = 0; if (get_contended_monitor != NULL && get_lock_owner_thread != NULL) { LOGI("get_contended_monitor != NULL"); //1、調用一下獲取monitor的函數,返回當前線程想要競爭的monitor int monitorObj = ((int (*)(long)) get_contended_monitor)(native_thread); if (monitorObj != 0) { LOGI("monitorObj != 0"); // 2、獲取這個monitor被哪個線程持有,返回該線程id monitor_thread_id = ((int (*)(int)) get_lock_owner_thread)(monitorObj); } else { LOGE("GetContendedMonitor return null"); monitor_thread_id = 0; } } else { LOGE("get_contended_monitor == NULL || get_lock_owner_thread == NULL"); } return monitor_thread_id; } ~~~ 兩個步驟: 1. 獲取當前線程要競爭的鎖 2. 獲取這個鎖被哪個線程持有 通過兩個步驟,得到的是那個一直不釋放鎖的線程id。 ## 通過算法,找到死鎖 前面已經知道當前blocked狀態的線程id(還需要轉換成native線程id),以及這個blocked線程在等待哪個線程釋放鎖,也就是得到關系鏈: 1. A等待B B等待A 2. A等待B B等待C C等待A ... 3. 其它... 如何判斷有死鎖?我們可以用Map來保存對應關系 map\[A\]=B map\[B\]=A 最后通過互斥條件判斷出死鎖線程,把造成死鎖的線程堆棧信息輸出,如下 ![](https://img.kancloud.cn/52/bd/52bd06c8b3aaf59ecddbe6c4b703e6b9_1240x176.png) 檢查出死鎖,線下可以彈窗或者toast,線上則可以采集數據上報。 ## 死鎖監控小結 死鎖監控原理還是比較清晰的: 1. 獲取blocked狀態的線程 2. 獲取該線程想要競爭的鎖(native層函數) 3. 獲取這個鎖被哪個線程持有(native層函數) 4. 有了關系鏈,就可以找出造成死鎖的線程 由于死鎖監控涉及到native層代碼,對于很多應用層開發的同學來說可能有點難度, # 參考資料 [《手Q Android線程死鎖監控與自動化分析實踐》](https://blog.csdn.net/Tencent_Bugly/article/details/79757867) [卡頓、ANR、死鎖,線上如何監控](https://juejin.cn/post/6973564044351373326)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看