java agent


cmd使用java -help可以看到關於agent參數:

1  -agentlib:<libname>[=<選項>]
2                加載本機代理庫 <libname>, 例如 -agentlib:hprof
3                另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help
4  -agentpath:<pathname>[=<選項>]
5                按完整路徑名加載本機代理庫
6  -javaagent:<jarpath>[=<選項>]
7                加載 Java 編程語言代理, 請參閱 java.lang.instrument

其實這三個參數做的事情是一樣的,都是java代理。

-agentlib和-agentpath使用的是本地代理也就是c/c++寫的本地庫(例如動態鏈接庫dll和靜態鏈接庫lib),

而-javaagent使用java語言編寫的jar。

關於這兩種用法,我舉兩個具體的例子供大家參考,具體如下:

1.使用-agentlib和-agentpath加載本地庫

我們使用C++來編寫本地庫,需要用到jdk中相關的頭文件,這里需要用到的6個頭文件在如下如下目錄中:

%JAVA_HOME%\include目錄下的:jawt.h, jni.h, jvmti.h, jvmticmlr.h

%JAVA_HOME%\include\win32目錄下的:jawt_md.h,  jni_md.h

我們使用visual studio 2017來編寫我們的項目:

首先新建一個dll項目,如下

點擊確定生成項目,然后把我們之前的6個頭文件添加進項目中,后項目結構如下:

然后查看jvmti.h文件中的三個函數,並將其copy到我們的agent.cpp中去實現(需要在cpp文件中引入我們的頭文件)

這里需要說明一點:

java agent有2個啟動函數分別為Agent_OnLoad和Agent_OnAttach
* Agent_OnLoad在onload階段被調用
* Agent_OnAttach在live階段被調用
* 但是每個agent只有一個啟動函數會被調用。

具體實現代碼如下:

  1 // Dll.cpp : 定義 DLL 應用程序的導出函數。
  2 //
  3 /*
  4 * The VM starts each agent by invoking a start-up function. 
  5 * If the agent is started in the OnLoad phase the function Agent_OnLoad will be invoked. 
  6 * If the agent is started in the live phase the function Agent_OnAttach will be invoked. 
  7 * Exactly one call to a start-up function is made per agent.
  8 * 中文總結一下上面的含義:
  9 * java agent有2個啟動函數分別為Agent_OnLoad和Agent_OnAttach
 10 * Agent_OnLoad在onload階段被調用
 11 * Agent_OnAttach在live階段被調用
 12 * 但是每個agent只有一個啟動函數會被調用
 13 */
 14 #include "stdafx.h"
 15 #include "jvmti.h"
 16 #include <iostream>
 17 
 18 /*
 19 * 此階段JVM還沒有初始化,所以能做的操作比較受限制
 20 * JVM參數都無法獲取
 21 * The return value from Agent_OnLoad is used to indicate an error. 
 22 * Any value other than zero indicates an error and causes termination of the VM.
 23 * 任何非零的返回值都會導致JVM終止。
 24 */
 25 
 26 
 27 JNIEXPORT jint JNICALL
 28 Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
 29     jvmtiEnv* jvmti;
 30     jvmtiCapabilities capabilities;
 31     options = options == nullptr ? "" : options;
 32     jint error = vm->GetEnv((void**)&jvmti, JVMTI_VERSION);
 33     if (error) {
 34         std::cout << "get jvmtiEnv error!\n";
 35     }
 36     else {
 37         jvmti->GetCapabilities(&capabilities);
 38         std::cout << "capabilities.can_access_local_variables : "
 39             << capabilities.can_access_local_variables
 40             << std::endl;
 41     }
 42 
 43     std::cout << "Agent_OnLoad\n" << "options ===== " << options << std::endl;
 44 
 45     
 46     
 47 
 48     return 0;
 49 }
 50 /*
 51 * Any value other than zero indicates an error. 
 52 * An error does not cause the VM to terminate. 
 53 * Instead the VM ignores the error, or takes some implementation specific action -- for example it might print an error to standard error, or record the error in a system log.
 54 *
 55 */
 56 JNIEXPORT jint JNICALL
 57 Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
 58     options = options == nullptr ? "" : options;
 59     std::cout << "Agent_OnAttach\n" << "options ===== " << options << std::endl;
 60 
 61     jvmtiEnv *jvmti;
 62     jint result = vm->GetEnv((void**)&jvmti, JVMTI_VERSION);//獲取jvmti指針
 63     jint count;//用於保存加載類的數量
 64     jclass* klasses;//指向保存已加載的類的指針
 65     jvmtiError error = jvmti->GetLoadedClasses(&count, &klasses);//獲取已加載的類和數量
 66     std::cout << "jvmtiError : " << error << std::endl;
 67     if (error)
 68         std::cout << "get loadedClasses error!\n";
 69     else {
 70         std::cout << "signatures : \n";
 71         for (int i = 0; i < count; i++) {//循環打印類的簽名
 72             char* signature;
 73             jvmti->GetClassSignature(klasses[i], &signature, NULL);
 74             std::cout << signature << std::endl;
 75         }
 76         std::cout << "signatures end! ------------------\n";//已加載類簽名打印結束標志
 77     }
 78     
 79 
 80 
 81     jint threadCount;//用於保存線程數量
 82     jthread* threads;//指向保存線程信息的指針
 83     error = jvmti->GetAllThreads(&threadCount, &threads);//獲取到線程信息
 84     if (error)
 85         std::cout << "get all threads error!\n";
 86     else {
 87         jvmtiThreadInfo threadInfo;//保存每個線程的信息,它是一個結構體的指針
 88         for (size_t i = 0; i < threadCount; i++) {
 89             error = jvmti->GetThreadInfo(threads[i], &threadInfo);
 90             if (error)
 91                 std::cout << "" << i << " 線程獲取出錯!\n";
 92             else
 93                 std::cout << "thread name:" << threadInfo.name << std::endl;
 94         }
 95         std::cout << "thread info end! ---------------------\n";
 96     }
 97 
 98 
 99     return 0;
100 }
101 /*
102 * This function can be used to clean-up resources allocated by the agent.
103 */
104 JNIEXPORT void JNICALL
105 Agent_OnUnload(JavaVM *vm) {
106     std::cout << "*********Agent_OnUnload**********\n";
107 
108 }

代碼編寫完之后生成項目:

這里注意下,生成的本地庫根據需要選擇生成X86和X64版本,我們這里選擇生成的是X64版本。生成的文件既有dll又有lib:

這兩個文件都可以使用,我們這里選擇使用dll文件。

下一步我們編寫一個java的main方法,然后在啟動jvm時加載我們的本地庫:

 

 

 

運行結果如下:

 

調用了Agent_OnLoad函數和Agent_OnUnload函數,因為Agent_OnLoad和Agent_OnAttach函數在一個agent中只有一個會被調用,如果你希望在JVM啟動時做些事情的話,就使用onload函數,如果希望有外部鏈接JVM時做一些工作的話就使用attach函數,unload函數在JVM關閉時調用。對於attach函數我們也可以在JVM運行時動態加載本地庫並且調用。這需要用到jdk中的tools.jar包,在%JAVA_HOME%\lib目錄中。舉一個例子:

我們先寫一個無限循環的小程序跑着

 

 然后通過jps找到它的進程號

進程號為10748

 下面是我們的代碼

 1 public class Test {
 2 
 3     
 4     public static void main(String[] args) throws AgentLoadException, AgentInitializationException, IOException, AttachNotSupportedException {
 5         attach();
 6     }
 7     
 8     public static void attach() throws AgentLoadException, AgentInitializationException, IOException, AttachNotSupportedException{
 9         String pid = "10748";//java進程號
10         String agentPath = "C:\\Users\\DanteJ\\source\\repos\\agent\\x64\\Debug\\agent.dll";//本地庫路徑
11         System.out.println("attaching....pid="+pid);
12         VirtualMachine virtualMachine = VirtualMachine.attach(pid);//attach JVM
13         virtualMachine.loadAgentPath(agentPath);//加載本地庫
14         virtualMachine.detach();//斷開
15     }
16 }

 

 下面是我們運行Loop的JVM打印的attach函數的運行結果

由此可見,我們可以動態的在已經運行的JVM中加載我們的本地代碼。

 

下面我們再來看-javaagent

 這里需要用到一個叫做premain的方法,它就像入口函數main方法一樣是一個固定用法,其函數原型申明如下:

public static void premain(String agentArgs, Instrumentation ins)

我們編寫一個測試類來作為演示

public class Agent {
    
    public static void premain(String agentArgs, Instrumentation ins){
        System.out.println("--------------javaagent-----------------");
    }

}

然后編寫MANIFEST.MF文件,必須要指定Premain-Class參數值

Manifest-Version: 1.0
Premain-Class: com.ideal.javaagent.Agent

然后將我們的測試項目打包成jar,然后下面代碼執行時在JVM參數上添加-javaagent:C:\Users\DanteJ\Desktop\test.jar,其中test.jar是我們剛才打好的包。

public static void main(String[] args) throws AgentLoadException, AgentInitializationException, IOException, AttachNotSupportedException {
        System.out.println("main");
    }

 

運行結果如下

成功地在main方法執行前,執行了我們的jar包中的premain方法,有關premain方法中的java.lang.instrument.Instrumentation參數,提供了很多獲取JVM參數的接口方法,這里

就不做深入了,如感興趣可以自行查閱資料。

 

 有關java agent的用法就介紹到這里,如果有什么解釋不到或者有誤的地方希望大神們指出。共同進步!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM