Java Instrumentation從概念到基本用法詳解
一、什么是 Java Instrumentation
Java Instrumentation 是 java.lang.instrument 包提供的 API,允許開發(fā)者在類被 JVM 加載時(shí)對(duì)其進(jìn)行修改,或者在運(yùn)行時(shí)重新定義類的字節(jié)碼。
主要用途
- 性能監(jiān)控與分析(如:JProfiler、YourKit)
- 代碼覆蓋率工具(如:JaCoCo、Cobertura)
- 動(dòng)態(tài)代理、AOP
- 安全檢查、沙箱實(shí)現(xiàn)
- 熱代碼替換(HotSwap)
二、核心概念
1. Java Agent
Instrumentation 的入口是 Java Agent,它是一種特殊的 jar 包,啟動(dòng) JVM 時(shí)通過 -javaagent 參數(shù)加載。
Agent Jar 需要在 MANIFEST.MF 里指定入口類:
Premain-Class: com.example.MyAgent
2. Premain & Agentmain
public static void premain(String agentArgs, Instrumentation inst)- JVM 啟動(dòng)時(shí)執(zhí)行(靜態(tài)加載)
public static void agentmain(String agentArgs, Instrumentation inst)- JVM 運(yùn)行中動(dòng)態(tài) attach(動(dòng)態(tài)加載)
3. Instrumentation 接口
這是核心接口,提供了如下能力:
- 添加 ClassFileTransformer
- 獲取已加載的類
- 重定義類(redefineClasses)
- 檢查是否支持 Retransform(isRetransformClassesSupported)
三、基本用法
1. 編寫 Agent
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("Agent loaded!");
inst.addTransformer(new MyTransformer());
}
}2. 實(shí)現(xiàn) ClassFileTransformer
public class MyTransformer implements ClassFileTransformer {
@Override
public byte[] transform(
ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer
) {
// 可以用 ASM/Javassist 修改字節(jié)碼
if (className.equals("com/example/TargetClass")) {
// 返回修改后的字節(jié)碼
}
return null; // 返回 null 表示不修改
}
}3. 啟動(dòng) JVM 時(shí)加載
java -javaagent:myagent.jar -jar myapp.jar
四、進(jìn)階應(yīng)用
1. 動(dòng)態(tài) Attach
JDK 提供了 com.sun.tools.attach.VirtualMachine 實(shí)現(xiàn)動(dòng)態(tài) attach agent 到運(yùn)行中的 JVM。
VirtualMachine vm = VirtualMachine.attach("pid");
vm.loadAgent("myagent.jar");2. 字節(jié)碼修改
常用工具有 ASM、Javassist、ByteBuddy。例如用 ASM 修改方法字節(jié)碼,實(shí)現(xiàn)方法增強(qiáng)、插樁。
3. 熱代碼替換
通過 Instrumentation.redefineClasses 可以在不重啟 JVM 的情況下替換已加載類的實(shí)現(xiàn)(有一定限制)。
五、典型案例
1. 性能監(jiān)控
在所有方法入口和出口插入計(jì)時(shí)代碼,統(tǒng)計(jì)每個(gè)方法的耗時(shí)。
2. 代碼覆蓋率
在每個(gè)分支、方法插入標(biāo)記,記錄哪些代碼被執(zhí)行過。
3. 安全防護(hù)
在敏感 API 調(diào)用前插入權(quán)限檢查邏輯。
六、注意事項(xiàng)
- 并非所有類都能被重新定義(如 JVM 內(nèi)部類、已被 native 方法鎖定的類)。
- 字節(jié)碼修改需謹(jǐn)慎,容易引發(fā)兼容性和穩(wěn)定性問題。
- 動(dòng)態(tài) attach 需要目標(biāo) JVM 開啟 attach 功能(通常為同一用戶)。
七、實(shí)戰(zhàn)案例
編寫一個(gè)Java Instrumentation接口性能監(jiān)控示例
1. 創(chuàng)建 Java Agent
PerformanceMonitorAgent.java
import java.lang.instrument.Instrumentation;
public class PerformanceMonitorAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("PerformanceMonitorAgent loaded.");
inst.addTransformer(new PerformanceTransformer());
}
}2. 編寫 Transformer
PerformanceTransformer.java
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class PerformanceTransformer implements ClassFileTransformer {
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
// 只監(jiān)控特定包下的類(比如com/example/)
if (className != null && className.startsWith("com/example/")) {
// 使用ASM或Javassist修改字節(jié)碼,在方法前后插入耗時(shí)統(tǒng)計(jì)代碼
try {
return MethodTimer.injectTimer(classfileBuffer);
} catch (Exception e) {
e.printStackTrace();
}
}
return classfileBuffer;
}
}3. 使用 Javassist 修改字節(jié)碼
MethodTimer.java
import javassist.*;
public class MethodTimer {
public static byte[] injectTimer(byte[] classfileBuffer) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(new java.io.ByteArrayInputStream(classfileBuffer));
for (CtMethod method : ctClass.getDeclaredMethods()) {
if (!method.isEmpty() && !method.getName().equals("<init>")) {
method.addLocalVariable("_startTime", CtClass.longType);
method.insertBefore("_startTime = System.nanoTime();");
method.insertAfter(
"System.out.println(\"[Performance] Method " + method.getName() +
" executed in \" + (System.nanoTime() - _startTime) + \" ns.\");"
);
}
}
byte[] byteCode = ctClass.toBytecode();
ctClass.detach();
return byteCode;
}
}4. 目標(biāo)類示例
com/example/Demo.java
package com.example;
public class Demo {
public void test() {
try { Thread.sleep(100); } catch (Exception e) {}
}
public static void main(String[] args) {
new Demo().test();
}
}5. 編譯 & 打包 Agent
javac -cp javassist.jar:. PerformanceMonitorAgent.java PerformanceTransformer.java MethodTimer.java jar cmf MANIFEST.MF perfagent.jar PerformanceMonitorAgent.class PerformanceTransformer.class MethodTimer.class
MANIFEST.MF 內(nèi)容:
Premain-Class: PerformanceMonitorAgent Can-Redefine-Classes: true Can-Retransform-Classes: true
6. 運(yùn)行目標(biāo)程序并加載 Agent
java -javaagent:perfagent.jar -cp javassist.jar:. com.example.Demo
7. 輸出示例
PerformanceMonitorAgent loaded.
[Performance] Method test executed in 100123456 ns.
說明:
- 這個(gè)例子用 Javassist 修改字節(jié)碼,在每個(gè)方法前后插入耗時(shí)統(tǒng)計(jì)代碼。
- 你可以根據(jù)需要修改
PerformanceTransformer,選擇需要監(jiān)控的類和方法。 - 實(shí)際生產(chǎn)環(huán)境建議將統(tǒng)計(jì)結(jié)果匯總、寫入日志或上報(bào)到監(jiān)控系統(tǒng)。
到此這篇關(guān)于Java Instrumentation詳解的文章就介紹到這了,更多相關(guān)Java Instrumentation內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java簡(jiǎn)單使用EasyExcel操作讀寫excel的步驟與要點(diǎn)
相信現(xiàn)在很多搞后端的同學(xué)大部分做的都是后臺(tái)管理系統(tǒng),那么管理系統(tǒng)就肯定免不了Excel的導(dǎo)出導(dǎo)入功能,下面這篇文章主要給大家介紹了關(guān)于Java簡(jiǎn)單使用EasyExcel操作讀寫excel的步驟與要點(diǎn),需要的朋友可以參考下2022-09-09
SpringBoot集成slf4j2日志配置的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot集成slf4j2日志配置的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08
簡(jiǎn)述Springboot @Async 異步方法
這篇文章主要介紹了Springboot @Async 異步方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-05-05
如何使用Spring工具類動(dòng)態(tài)匹配url
這篇文章主要介紹了如何使用Spring工具類動(dòng)態(tài)匹配url,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
MapStruct @Mapping注解之處理映射中的Null值方式
這篇文章主要介紹了MapStruct @Mapping注解之處理映射中的Null值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Eclipse的Debug調(diào)試技巧大全(總結(jié))
這篇文章主要介紹了Eclipse的Debug調(diào)試技巧大全(總結(jié)),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12
掌握SpringMVC中@InitBinder的實(shí)際應(yīng)用
這篇文章主要介紹了掌握SpringMVC中@InitBinder的實(shí)際應(yīng)用,@InitBinder是Spring MVC框架中的一個(gè)注解,用于自定義數(shù)據(jù)綁定的方法,通過在控制器中使用@InitBinder注解,可以將特定的數(shù)據(jù)綁定邏輯應(yīng)用于請(qǐng)求參數(shù)的處理過程中,需要的朋友可以參考下2023-10-10

