一、什么是 java instrumentation
java instrumentation 是 java.lang.instrument 包提供的 api,允许开发者在类被 jvm 加载时对其进行修改,或者在运行时重新定义类的字节码。
主要用途
- 性能监控与分析(如:jprofiler、yourkit)
- 代码覆盖率工具(如:jacoco、cobertura)
- 动态代理、aop
- 安全检查、沙箱实现
- 热代码替换(hotswap)
二、核心概念
1. java agent
instrumentation 的入口是 java agent,它是一种特殊的 jar 包,启动 jvm 时通过 -javaagent 参数加载。
agent jar 需要在 manifest.mf 里指定入口类:
premain-class: com.example.myagent
2. premain & agentmain
public static void premain(string agentargs, instrumentation inst)- jvm 启动时执行(静态加载)
public static void agentmain(string agentargs, instrumentation inst)- jvm 运行中动态 attach(动态加载)
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. 实现 classfiletransformer
public class mytransformer implements classfiletransformer {
@override
public byte[] transform(
classloader loader, string classname, class<?> classbeingredefined,
protectiondomain protectiondomain, byte[] classfilebuffer
) {
// 可以用 asm/javassist 修改字节码
if (classname.equals("com/example/targetclass")) {
// 返回修改后的字节码
}
return null; // 返回 null 表示不修改
}
}3. 启动 jvm 时加载
java -javaagent:myagent.jar -jar myapp.jar
四、进阶应用
1. 动态 attach
jdk 提供了 com.sun.tools.attach.virtualmachine 实现动态 attach agent 到运行中的 jvm。
virtualmachine vm = virtualmachine.attach("pid");
vm.loadagent("myagent.jar");2. 字节码修改
常用工具有 asm、javassist、bytebuddy。例如用 asm 修改方法字节码,实现方法增强、插桩。
3. 热代码替换
通过 instrumentation.redefineclasses 可以在不重启 jvm 的情况下替换已加载类的实现(有一定限制)。
五、典型案例
1. 性能监控
在所有方法入口和出口插入计时代码,统计每个方法的耗时。
2. 代码覆盖率
在每个分支、方法插入标记,记录哪些代码被执行过。
3. 安全防护
在敏感 api 调用前插入权限检查逻辑。
六、注意事项
- 并非所有类都能被重新定义(如 jvm 内部类、已被 native 方法锁定的类)。
- 字节码修改需谨慎,容易引发兼容性和稳定性问题。
- 动态 attach 需要目标 jvm 开启 attach 功能(通常为同一用户)。
七、实战案例
编写一个java instrumentation接口性能监控示例
1. 创建 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) {
// 只监控特定包下的类(比如com/example/)
if (classname != null && classname.startswith("com/example/")) {
// 使用asm或javassist修改字节码,在方法前后插入耗时统计代码
try {
return methodtimer.injecttimer(classfilebuffer);
} catch (exception e) {
e.printstacktrace();
}
}
return classfilebuffer;
}
}3. 使用 javassist 修改字节码
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. 目标类示例
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 内容:
premain-class: performancemonitoragent can-redefine-classes: true can-retransform-classes: true
6. 运行目标程序并加载 agent
java -javaagent:perfagent.jar -cp javassist.jar:. com.example.demo
7. 输出示例
performancemonitoragent loaded.
[performance] method test executed in 100123456 ns.
说明:
- 这个例子用 javassist 修改字节码,在每个方法前后插入耗时统计代码。
- 你可以根据需要修改
performancetransformer,选择需要监控的类和方法。 - 实际生产环境建议将统计结果汇总、写入日志或上报到监控系统。
到此这篇关于java instrumentation详解的文章就介绍到这了,更多相关java instrumentation内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论