java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java Instrumentation

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

作者:燎原人生

Java Instrumentation是java.lang.instrument包提供的 API,允许开发者在类被JVM 加载时对其进行修改,或者在运行时重新定义类的字节码,本文给大家介绍Java Instrumentation从概念到基本用法详解,感兴趣的朋友一起看看吧

一、什么是 Java Instrumentation

Java Instrumentation 是 java.lang.instrument 包提供的 API,允许开发者在类被 JVM 加载时对其进行修改,或者在运行时重新定义类的字节码。

主要用途

二、核心概念

1. Java Agent

Instrumentation 的入口是 Java Agent,它是一种特殊的 jar 包,启动 JVM 时通过 -javaagent 参数加载。

Agent Jar 需要在 MANIFEST.MF 里指定入口类:

Premain-Class: com.example.MyAgent

2. Premain & Agentmain

3. Instrumentation 接口

这是核心接口,提供了如下能力:

三、基本用法

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 调用前插入权限检查逻辑。

六、注意事项

七、实战案例

编写一个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.

说明:

到此这篇关于Java Instrumentation详解的文章就介绍到这了,更多相关Java Instrumentation内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文