当前位置: 代码网 > it编程>编程语言>Java > Java Instrumentation从概念到基本用法详解

Java Instrumentation从概念到基本用法详解

2025年09月25日 Java 我要评论
一、什么是 java instrumentationjava instrumentation是java.lang.instrument包提供的 api,允许开发者在类被 jvm 加载时对其进行修改,或

一、什么是 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内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com