一個簡單JavaAgent的實現

一、什么是javaagent

javaagent是一個JVM“插件”,一種專門精心制作的.jar文件,它能夠利用JVM提供的Instrumentation API。

1.1、概要

Java Agent由三部分組成:代理類、代理類元信息和JVM加載.jar和代理的機制,整體內容如下圖所示:
在這里插入圖片描述

1.2、javaagent的基石

java.lang.instrument 為javaagent 通過修改方法字節碼的方式操作運行在JVM上的程序提供服務。javaagent以JAR包的形式部署,JAR文件清單中的屬性指定要加載的代理類,以啟動代理。

javaagent的啟動方式有以下幾種:

  • 通過在命令行指定參數啟動。

  • JVM啟動后啟動。例如,提供一種工具,該工具可以依附到已運行的應用,并允許在已運行的應用內加載代理。

  • 與應用一起打包為可執行文件。

1.3、啟動 javaagent

1.3.1、命令行啟動

命令行啟動參數如下:

-javaagent:<jarpath>[=<options>]

<jarpath> :javaagent的路徑,比如 /opt/var/Agent-1.0.0.jar
<options> : javaagent參數,參數的解析由javaagent負責。
javaagent JAR文件清單必須包含 Premain-Class 屬性,屬性的值為agent class的全路徑名(包名+類名)。代理類必須實現 premain 方法,premain 方法和 main 方法一樣分別是代理和應用的入口點。JVM初始化完成后首先調用代理的premain函數,然后調用應用的main函數,premain方法必須返回后進程才能啟動。

premain 方法簽名如下:

public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)

JVM首先嘗試在代理中調用簽名為1的方法,如果代理類沒有實現簽名為1的方法,JVM嘗試調用簽名為2的方法:

代理類可以有一個 agentmain函數,函數會在JVM啟動完成之后調用。如果,使用命令行啟動代理,agentmain 方式不會被調用。

代理的所有參數被當作一個字符串通過 agentArgs 變量傳遞,代理負責解析參數字符串。

如果代理因為代理類無法被加載、代理類未實現 premain 方法或拋出了未被捕獲的異常,JVM將會退出。

javaagent的啟動不要求實現一定提供命令行的方式,如果,實現支持通過命令行啟動,實現必須支持在命令行中通過指定 -javaagent 參數啟動。 -javaagent 可以在命令行中使用多次,啟動多個代理。premain 函數的調用順序和命令行中指定的順序一致,多個代理可以使用相同 <jarpath>

沒有一個嚴格模型來定義 premain 函數的工作范圍,任何 main 函數可以做的工作,比如創建線程,在 premain 函數中都是合法的。

1.3.2、JVM啟動后啟動

實現可以提供在JVM啟動之后再啟動代理的機制。代理如何啟動的細節特定于實現,通常應用程序已經啟動,并且它的 main 方法已經被調用。如果實現支持在JVM啟動后啟動代理,代理必須滿足以下條件:

  • 清單文件包含 Agent-Class 屬性,屬性的值為代理類全名。

  • 代理類必須實現 public static agentmain 方法。

agentmain方法有以下兩個函數簽名:

public static void agentmain(String agentArgs, Instrumentation inst)
public static void agentmain(String agentArgs)

JVM首先嘗試調用具有簽名1的方法,如果,代理類沒有實現該方法,JVM嘗試調用簽名為2的方法。

代理類可以同時實現 premainagentmain 兩個方法,當代理以命令行方式啟動時,JVM調用 premain 函數,當代理在JVM啟動之后啟動時,JVM調用 agentmain 函數,而且JVM不會調用 premain 函數。

agentmain 函數參數的傳遞也是通過 agentArgs,所有參數組合為一個字符串,參數的解析由代理負責。

agentmain 函數必須完成啟動代理所有必須的初始化動作,當啟動完成后,agentmain 函數必須返回。如果,代理不能啟動或拋出未捕獲的異常,JVM都會退出。

1.3.3、打包為可執行文件

如果代理打包到可執行JAR文件中,可執行JAR文件的清單中必須包含 Launcher-Agent-Class 屬性,指定一個在應用main函數調用之前代理啟動的類。JVM嘗試在代理上調用以下方法:

public static void agentmain(String agentArgs, Instrumentation inst)

如果,代理類沒有實現上述方法,JVM則調用下面的方法。

public static void agentmain(String agentArgs)

agentArgs 參數的值必須為空字符串。

agentmain 函數必須完成代理啟動必須的所有初始化動作并在啟動后返回。如果,代理無法啟動或拋出未捕獲的異常,JVM會退出。

1.3.4、加載代理類以及代理類可用的模塊/類

系統類加載器負責加載代理JAR文件中的所有類,并且成為系統類加載器的未命名模塊的成員。 系統類加載器通常也定義包含應用程序 main 方法的類。對代理類可見的所有類都對系統類加載器可見,必須滿足下面的最低要求:

  • 啟動層中的模塊導出的包中的類。 啟動層是否包含所有平臺模塊取決于初始模塊或應用程序的啟動方式。

  • 類可被系統類加載器定義。

  • 啟動類加載器定義的所有代理的類為其未命名模塊的成員。

如果代理類需要鏈接到不在啟動層中的平臺(或其他)模塊中的類,則需要以確保這些模塊位于啟動層中的方式啟動應用程序。 例如,在JDK實現中,--add-modules 命令行選項可用于將模塊添加到要在啟動時解析的根模塊集中。

啟動類加載器可以加載代理支持的類(通過 appendToBootstrapClassLoaderSearch 或指定Boot-Class-Path屬性)必須僅鏈接到定義啟動類加載器的類。 無法保證啟動類加載器可以在所有平臺工作。

如果配置了自定義系統類加載器(通過 getSystemClassLoader 方法中指定的系統屬性 java.system.class.loader ),則必須定義 appendToSystemClassLoaderSearch 中指定的 appendToClassPathForInstrumentation 方法。 換句話說,自定義系統類加載器必須支持將代理JAR文件添加到系統類加載器搜索范圍內的機制。

1.4、javaagent清單屬性

屬性說明是否必選默認值
Premain-Class包含premain方法的類依賴啟動方式
Agent-Class包含agentmain方法的類依賴啟動方式
Boot-Class-Path啟動類加載器搜索路徑
Can-Redefine-Classis是否可以重定義代理所需的類false
Can-Retransform-Classis是否能夠重新轉換此代理所需的類false
Can-Set-Native-Method-Prefix是否能夠設置此代理所需的本機方法前綴false

二、寫一個Java Agent

基于上面的介紹,我們實現一個下載JVM中所有非系統類的javaagent。

整個開發過程包括以下三步:

  • 1)定義代理類,實現類下載功能;

  • 2)配置、打包;

  • 3)命令行啟動測試。

2.1、代理類實現

實現 premain 函數

package io.ct.java.agent;import java.lang.instrument.Instrumentation;public class AgentApplication {public static void premain(String arg, Instrumentation instrumentation) {System.err.println("agent startup , args is " + arg);// 注冊我們的文件下載函數instrumentation.addTransformer(new DumpClassesService());}
}

文件下載類實現 ClassFileTransformer 接口,在類被加載時下載類的字節碼:

package io.ct.java.agent;import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.List;/*** Copyright (C), 2018-2018, open source* FileName: DumpClassesService** @author : 大哥* Date:     2018/12/8 21:01*/
public class DumpClassesService implements ClassFileTransformer {private static final List<String> SYSTEM_CLASS_PREFIX = Arrays.asList("java", "sum", "jdk");@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if (!isSystemClass(className)) {System.out.println("load class " + className);FileOutputStream fos = null;try {// 將類名統一命名為classNamedump.class格式fos = new FileOutputStream(className + "dump.class");fos.write(classfileBuffer);fos.flush();} catch (IOException ioe) {ioe.printStackTrace();} finally {// 關閉文件輸出流if (null != fos) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}return classfileBuffer;}/*** 判斷一個類是否為系統類** @param className 類名* @return System Class then return true,else return false*/private boolean isSystemClass(String className) {// 假設系統類的類名不為NULL而且不為空if (null == className || className.isEmpty()) {return false;}for (String prefix : SYSTEM_CLASS_PREFIX) {if (className.startsWith(prefix)) {return true;}}return false;}
}

2.2、配置MANIFEST.MF

MANIFEST.MF 文件兩種方式生成:手動配置和自動生成,手動配置只需要在 resources 文件下創建 META-INF/MENIFEST.MF 文件即可。除去手動配置外,可以使用maven插件在打包階段自動生成,maven的插件配置如下:

             <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><manifestEntries><Premain-Class>io.ct.java.agent.AgentApplication</Premain-Class><Agent-Class>io.ct.java.agent.AgentApplication</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin>

生成的jar包格式如下:
在這里插入圖片描述
其中MANIFEST.MF的文件內容如下(不同的配置生成的文件內容不完全一致):

Manifest-Version: 1.0
Implementation-Title: agent
Premain-Class: io.ct.java.agent.AgentApplication
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: chentong
Agent-Class: io.ct.java.agent.AgentApplication
Can-Redefine-Classes: true
Implementation-Vendor-Id: io.ct.java
Can-Retransform-Classes: true
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_171
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-boot-starter-parent/agent

2.3、命令行啟動Java Agent

執行下面的命令,運行已經編譯好的類Hello,可以在同級目錄下生成一個名為Hellodump.class的文件。

java -javaagent:agent-0.0.1-SNAPSHOT.jar Hello

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446901.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446901.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446901.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Openai的openai新版本調用方式

最近大家有沒有發現Openai的openai已經更新到1.6.1了,而且API的調用方式發生了巨大的變化,下面來看看openai新的調用方式吧。 歡迎關注公眾號 module ‘openai’ has no attribute ChatCompletion. 提示openai的版本過低。(pip install -U openai) 1. Chat API from openai…

計算機系統基礎 計算機系統的基本組成與基本功能

基礎知識點 1.1946年第一臺通用電子計算機ENIAC誕生 2.馮.諾依曼結構: 組成:輸入設備,輸出設備,存儲器,運算器,控制器 3.現代計算機結構模型: 組成 CPU中央處理器 PC程序計數器 IR指令寄存器 ALU算數邏輯部件 GPRs通用寄存器組 MAR存儲器地址寄存器 MDR存儲器數據寄存器 知…

Android UI卡頓監控

一、背景 應用的使用流暢度&#xff0c;是衡量用戶體驗的重要標準之一。Android 由于機型配置和系統的不同&#xff0c;項目復雜App場景豐富&#xff0c;代碼多人參與迭代歷史較久&#xff0c;代碼可能會存在很多UI線程耗時的操作&#xff0c;實際測試時候也會偶爾發現某些業務…

linux查看內核版本信息

使用命令 uname -acat /proc/version

C語言學習:snprintf()函數

函數原型&#xff1a; int snprintf(char* dest_str,size_t size,const char* format,...);函數功能&#xff1a; 先將可變參數 “...” 按照format的格式格式化為字符串&#xff0c;然后再將其拷貝至dest_str中。 頭文件&#xff1a; #include<stdio.h>注意事項&…

物理 質點運動學

常用公式 重點 1.求軌道方程:消去時間t 2.dr---->位置矢量大小的增量 3.求方向:tanax/y 4.求位置也就是求位移rxiyj即可 習題解析 1.求運動時一定要求出加速度,變速與勻速就是看a 2.求位移時必須看X0是不是為0,如果不為0,求位移與路程時都要減去x0 3. 記住等號兩邊統一…

使用CLion的時候,對于cmake的使用

問題概述 使用CLion的時候&#xff0c;一個大的項目會有一個總的CMakeLists.txt&#xff0c;這個是控制整個項目的編譯環境&#xff0c;但是針對測試的代碼會有自己的單獨的CMakeLists.txt&#xff0c;這個單獨的cmake文件是控制自己的程序所需要的環境即使是編譯單獨的測試程…

Android NDK之靜態/動態注冊Native方法

一、簡介 關于NDK有兩種方法注冊&#xff1a;靜態注冊和動態注冊。 靜態注冊&#xff1a; 就是直接在Java文件里寫個native方法 然后再c/c文件中實現這個方法就行了&#xff1b;動態注冊&#xff1a; 就是為了不要寫很長的方法名&#xff0c;用JNI_OnLoad方法實現預注冊&…

概率論 條件概率 全概率 貝葉斯公式

常用知識點 條件概率 1.P(B|A)1表示A發生的情況下B必然發生 A屬于B 2.可列可加性 P(BUC|A)P(B|A)P(C|A) 3.P(B|A)的樣本空間為A,A與B都發生了 大題解答思路 1.首先設取出一件商品為次品為事件A 2.寫B1:甲生產,B2:乙生產 PB1…PB2… P(A|B1)…P(A|B2)… 3.寫PAPB1*P(A|B1)……

使用命令行的方式,將ini配置文件中的配置信息傳遞給程序

ini配置文件 {"device_type": "fake","device_socket": "192.168.1.108:5000"} 使用rpc的方式 ./bin/hsm_device_apitest --gtest_filter"*aes_test" --device-type rpc --device-socket 192.168.1.108:5000 使用fake的方…

C語言學習:malloc()函數

函數聲明&#xff1a; void *malloc(size_t size)頭文件&#xff1a; #include <stdio.h>函數描述&#xff1a; 分配所需的內存空間&#xff0c;并返回一個指向它的指針。 參數&#xff1a; size – 內存塊的大小&#xff0c;以字節為單位。 返回值&#xff1a; 該…

java 希爾排序

希爾排序(更高效的插入排序) 減少最小數在最后一位的情況下要循環的次數 思路: 把數組按增量(n/2)分組,對每一組使用插入排序去排序交換位置,然后不停地增量/2,直到其為1時,結束 分組:如n/25 891723 8與3為一組 從不包含本身的數開始數兩種實現方法: 交換法(效率較低) 移動法…

使用gtest進行自己的單獨測試的代碼介紹

命令行 ./bin/hsm_device_apitest --gtest_filter"*aes_test" --device-type rpc --device-socket 192.168.1.108:5000 命令詳解 進入工程文件&#xff0c;mkdir build&#xff0c;cd build在build的文件夾下面執行cmake命令和make命令之后&#xff0c;會在build文…

C語言學習:%d、2d、02d、.2d的區別

%d&#xff1a;為普通的輸出。 %2d&#xff1a;按寬度為2輸出&#xff0c;右對齊方式輸出。若不夠兩位&#xff0c;左邊補空格。 %02d&#xff1a;同樣寬度為2&#xff0c;右對齊方式。位數不夠&#xff0c;左邊補0。 %.2d&#xff1a;從執行效果來看&#xff0c;與%02d一樣…

計算機系統基礎 數據的表示和存儲

數制和編碼 1.信息的二進制編碼 2.進制轉換必須要知道: 1)使用哪一個進制(二,八…) 2)定點數還是浮點數(關于小數點的問題) 3)編碼問題----原碼,補碼,反碼,移碼 3.進制轉換 1)R進制轉十進制(按權展開) ----R進制 ----八進制與十六進制 ----R轉換為十進制 2)十進制轉換為R…

C++中vector章節iterator與const_iterator及const iterator區別

C目前傾向于使用迭代器遍歷容器中的元素&#xff0c;而不是使用下標訪問的方式來訪問容器中的元素。可以使用iterator和const_iterator來訪問元素&#xff0c;但是const類型的容器&#xff0c;那么只能用const_iterator來遍歷。區別在于iterator可以改變元素的數值&#xff0c;…

Android查看當前應用已經加載的so庫

源代碼&#xff1a; private static List<String> allSOLists new ArrayList<String>();/** * 獲取全部已加載的SO庫*/private void getAllSOLoaded(){allSOLists.clear();// 當前應用的進程IDint pid Process.myPid();String path "/proc/" pid &q…

Android 進程監控(top命令)

文章目錄一、查看top命令Android N&#xff08;7.1系統&#xff0c;level 25&#xff09; 及之前Android O&#xff08;8.0系統&#xff0c;level 26&#xff09; 及之后二、top -n [number]Android N&#xff08;7.1系統&#xff0c;level 25&#xff09; 及之前Android O&…

java 快速排序

快速排序 對冒泡排序的一種改進 思路: 一趟排序后,選取一個中間值,數組被分為比中間值小的部分,比中間值大的部分;再對左右兩部分分別遞歸排序 代碼實現 import java.util.Arrays;public class QuickSort {public static void main(String[] args) {int[] arr {-9, 78, 0, 2…