java異常查看利器之使用 jvmti 的Callback_JVMTI_EVENT_EXCEPTION 事件查看異常

来源:https://www.cnblogs.com/yql1986/archive/2018/09/24/9695319.html
-Advertisement-
Play Games

在開源文件大行其道的今天,基於java種種解決方案和框架紛繪踏至而來,猶如浩瀚如海,看不完也學不盡。在採用這些解決方案和框架進行項目開發時,往往會出現當程式卡殼時,既無異常提示信息亦沒有與之對應的日誌輸出的局面。每每出現這樣的困境時,往往只能通過打斷點來一步一步調試跟蹤來解決。更有甚者,基於某一底層... ...


  閱讀本文前需要瞭解什麼是jvmtijvmti全稱稱之為 JVM Tool Interface,有關jvmti更詳細的知識,本文不再詳細列出。大家可以藉助百度來瞭解有關它更為詳盡的內容。

  在開源文件大行其道的今天,基於java種種解決方案和框架紛繪踏至而來,浩瀚如海看不完也學不盡。在採用這些解決方案和框架進行項目開發時,往往會出現當程式卡殼時,既無異常提示信息亦沒有與之對應的日誌輸出的局面。每每出現這樣的困境時,往往只能通過打斷點來一步步調試跟蹤來解決。更有甚者,基於某一底層的框架進行相應的開發時,受限於框架開發者的精力和時間等因素的影響,如果框架針對某異常處理設計的不合理,處理異常時沒有向外拋出異常,同時又沒有輸出日誌信息。當出現問題時,雪上加霜的是框架又沒有提供源碼用於打斷點調試,此時只能藉助通過反編譯工具,閱讀框架源碼來嘗試解決問題。每每出現這些困境,真希望有一種工具能夠洞悉那些被框架“吃掉”沒有嚮往拋出的異常,以便加快問題的解決步伐。

  為了方便開發,一直都想做一個有關java異常查看的小工具。想了很長時間,想到瞭如下幾種實現方式:

  • 藉助位元組碼工具,在每一個方法開頭和結尾處插入java異常捕獲代碼。這種方式實現起來效率太低了,況且如果在方法體內,捕獲異常並沒有向外拋出的話,就算採用這種方式也看不到異常。
  • SpringMVC框架針對異常進行了統一的封裝和處理,只要進行相應的擴展就能捕獲到程式拋出的異常。這種實現方式較前一種比較看來,效率大大提高了,但是仍然沒有解決前者提到的,如果應用程式內部自己“吃掉異常”,不向外拋出異常的話,依然無法捕捉到異常,而且這種實現實現方式僅僅局限於使用了SpringMVC框架的WEB應用程式,如果使用了其它的WEB架構或者非WEB的應用程式就會無能為力,局限性太強。

  思來索去,想到java應用程式的運行肯定是離不開jvm的,不妨看一下jvm中有沒有提供這樣的擴展。在網上搜索了一番,發現jvm還真提供了這樣的擴展。

JVM Tool Interface 鏈接地址:https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#Exception


 

示例代碼,在main方法中吃掉異常之後,不作任何處理。

 1 package com.github.torlight.jvmtit;
 2 
 3 /**
 4  * Hello world!
 5  *
 6  */
 7 public class App {
 8     
 9     public static void main( String[] args ){
10         
11         System.out.println( "Hello World!" );
12       
13         try {
14             throw new NullPointerException("QQQ");
15         } catch (Exception e) {
16             
17         }    
18     }
19 }

  程式載入相應的擴展,運行之後效果如下所示,可以看到在控制臺上面,列印出空指針異常。如果不藉助jvmti提供的異常事件進行相應的擴展話,控制臺上就不會列印空指針異常信息。其實現原理也很簡單,藉助jvmti提供的異常事件進行相應的擴展,當jvm捕獲到異常時,會回調針對該事件的擴展方法,在該方法體內部調用 printStackTrace 方法,列印異常提示信息。

 1 java.lang.ClassNotFoundException: com.github.torlight.jvmtit.App
 2     at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
 3     at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
 4     at java.lang.ClassLoader.loadClass(ClassLoader.java:411)
 5     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
 6     at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 7     at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
 8 java.lang.ClassNotFoundException: com.github.torlight.jvmtit.App
 9     at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
10     at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
11     at java.lang.ClassLoader.loadClass(ClassLoader.java:411)
12     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
13     at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
14     at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
15 java.lang.NullPointerException: QQQ
16     at com.github.torlight.jvmtit.App.main(App.java:14)
17 Hello World!
18 loaded class name=run in Callback_JVMTI_EVENT_EXCEPTION method
19  Exception: Ljava/lang/ClassNotFoundException;
20 loaded class name=run in Callback_JVMTI_EVENT_EXCEPTION method
21  Exception: Ljava/lang/ClassNotFoundException;
22 loaded class name=run in Callback_JVMTI_EVENT_EXCEPTION method
23  Exception: Ljava/lang/NullPointerException;
24 agent onload

下麵貼出針對jvmti Callback_JVMTI_EVENT_EXCEPTION 事件進行擴展的agent代碼。

  1 // 這是主 DLL 文件。
  2 
  3 #include "stdafx.h"
  4 
  5 #include "jvmti_evt_ex.h"
  6 #include <stdio.h>
  7 #include <memory.h>
  8 #include <string.h>
  9 #include <jvmti.h>
 10 
 11 void printStackTrace(JNIEnv* env, jobject exception) {
 12     jclass throwable_class = (*env).FindClass("java/lang/Throwable");
 13     jmethodID print_method = (*env).GetMethodID(throwable_class, "printStackTrace", "()V");
 14     (*env).CallVoidMethod(exception, print_method);
 15 }
 16 
 17 void JNICALL Callback_JVMTI_EVENT_EXCEPTION (jvmtiEnv *jvmti_env,
 18     JNIEnv* jni_env,
 19     jthread thread,
 20     jmethodID method,
 21     jlocation location,
 22     jobject exception,
 23     jmethodID catch_method,
 24     jlocation catch_location) {
 25 
 26     printf("loaded class name=%s\n ", "run in Callback_JVMTI_EVENT_EXCEPTION method");
 27     char* class_name;
 28 
 29     jclass exception_class = jni_env->GetObjectClass(exception);
 30     jvmti_env->GetClassSignature(exception_class, &class_name, NULL);
 31     printf("Exception: %s\n", class_name);    
 32 
 33     printStackTrace(jni_env, exception);
 34 }
 35 
 36 
 37 void JNICALL Callback_JVMTI_EVENT_Exception_Catch (jvmtiEnv *jvmti_env,
 38     JNIEnv* jni_env,
 39     jthread thread,
 40     jmethodID method,
 41     jlocation location,
 42     jobject exception)    {
 43 
 44     char* class_name;
 45     jclass exception_class = jni_env->GetObjectClass(exception);
 46     jvmti_env->GetClassSignature(exception_class, &class_name, NULL);
 47     printf("Exception: %s\n", class_name);    
 48 
 49     printStackTrace(jni_env, exception);    
 50 }
 51 
 52 
 53 JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved){
 54     jvmtiEnv *jvmti = NULL;
 55     
 56     fprintf(stderr,"agent onload");
 57 
 58     //獲取JVMTI environment
 59     jint erno = vm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_1);
 60     if (erno != JNI_OK) {
 61         fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
 62         return JNI_ERR;
 63     }
 64     
 65     //註冊功能
 66     jvmtiCapabilities capabilities;
 67     (void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));
 68     capabilities.can_generate_exception_events=1;
 69 
 70     jvmtiError error = jvmti->AddCapabilities(&capabilities);
 71     if(error != JVMTI_ERROR_NONE) {
 72         fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
 73         return  error;
 74     }
 75 
 76     //設置JVM事件 (JVMTI_EVENT_EXCEPTION) 回調
 77     jvmtiEventCallbacks ex_callbacks;
 78     ex_callbacks.Exception = &Callback_JVMTI_EVENT_EXCEPTION;
 79     error = jvmti->SetEventCallbacks(&ex_callbacks, (jint)sizeof(ex_callbacks));
 80     if(error != JVMTI_ERROR_NONE) {
 81         fprintf(stderr, "ERROR: Unable to SetEventCallbacks JVMTI!");        
 82         return error;
 83     }
 84 
 85     //設置事件通知
 86     error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, (jthread)NULL);
 87     if(error != JVMTI_ERROR_NONE) {
 88         fprintf(stderr, " ERROR: Unable to SetEventNotificationMode JVMTI!,the error code=%d",error);
 89         return  error;
 90     }
 91 
 92     return JNI_OK;
 93 }
 94 
 95 JNIEXPORT jint JNICALL
 96     Agent_OnAttach(JavaVM* vm, char *options, void *reserved){
 97         //do nothing
 98         
 99     return JNI_OK;
100 }
101 
102 JNIEXPORT void JNICALL
103     Agent_OnUnload(JavaVM *vm){
104         //do nothing
105     
106 }

 

  示例代碼和agent代碼均已經上傳至github上面(鏈接地址:https://github.com/gittorlight/java-other/tree/master/jvmti_evt_ex),我是用 visual studio 2010 來編譯agent的,編譯的時候需要根據所下載的jdk是32位還是64位來選擇相對應的頭文件。我使用的是64位的jdk 1.8,所以使用的是64位的頭文件。截圖如下所示:

  編譯agent截圖(一)

 

編譯agent截圖(二)

 

編譯agent截圖(三)

 

 編譯完成agent之後,在應用程式的啟動參數上面使用-agentpath 參數來載入該agent。以eclipse為例,截圖如下所示:

載入agent截圖(一)

 

 

載入agent截圖(二)

 

 

載入agent截圖(三)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一.概述 Java不同於C/C++這類傳統的編譯型語言,也不同於php這一類動態的腳本語言。可以說Java是一種半編譯語言,我們所寫的類會先被編譯成.class文件,這個.class是一串二進位的位元組流。然後當要使用這個類的時候,就會將這個類對應的.class文件載入進記憶體中。而將這個.class的 ...
  • 一、contextMap中的數據操作 root根:List 元素1 元素2 元素3 元素4 元素5 contextMap:Map key value application Map key value name test session Map request Map attr Map 1、存數據: ...
  • 在上一篇《你真的懂ReentrantReadWriteLock嗎?》中我給大家留了一個引子,一個更高效同時可以避免寫饑餓的讀寫鎖 StampedLock。StampedLock實現了不僅多個讀不互相阻塞,同時在讀操作時不會阻塞寫操作。 為什麼StampedLock這麼神奇?能夠達到這種效果,它的核心 ...
  • Java集合框架中的List與Set 為列表,中在列表中的對象是由順序的排序的,並且是有重覆的對象。 簡單為:有序,有重覆。 為集合,在集合中的對象是不按照順序排列的,並且是沒有重覆的對象的。 簡單為:無序,無重覆。 為無序集合,無序無重覆; 為有序集合,有序有重覆; 案例 知識點 返回迭代的下一個 ...
  • %還是format 1、皇城PK Python中格式化字元串目前有兩種陣營:%和format,我們應該選擇哪種呢? 自從Python2.6引入了format這個格式化字元串的方法之後,我認為%還是format這根本就不算個問題。不信你往下看。 上面的代碼很明顯會拋出一個如下的TypeError: T ...
  • 閑來無事,買了一個雲伺服器來玩玩。想要做的第一件事情就是搭建web項目,查詢了網上的資料得知,部署web項目的步驟是 (1)配置java環境(這裡選擇的是jdk1.8) (2)配置tomcat伺服器(這裡選擇的是tomcat7) (3)上傳web項目 (4)訪問web項目 那麼就開始我們的第一步和第 ...
  • 隨著微處理機技術的發展,人們只需花幾百美元就能買到一個CPU晶元,這個晶元每秒鐘執行的指令比80年代最大的大型機的處理機每秒鐘所執行的指令還多。如果你願意付出兩倍的價錢,將得到同樣的CPU,但它卻以更高的時鐘速率運行。因此,最節約成本的辦法通常是在一個系統中使用集中在一起的大量的廉價CPU。所以,傾... ...
  • 在我們的應用程式中日誌是不可缺少的部分,在Apache中由一個功能無比強大的日誌組件,它提供了方便的日誌記錄,這個開源的項目就是我們慣用的Log4j,jar包我麽們一顆取Apache官網下載最新版本的。log4j下載地址 一.入門篇 1.打開我們的Eclipse新建一個Java項目並且導入Log4j ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...