JavaAgent入门

191 阅读2分钟

入门

agent

import java.lang.instrument.ClassFileTransformer;  
import java.lang.instrument.IllegalClassFormatException;  
import java.lang.instrument.Instrumentation;  
import java.security.ProtectionDomain;  
  
/**  
* 描述  
*/  
public class PerfMonitorAgent {  
  
/**  
* 执行方法拦截  
*  
* @param agentArgs:-javaagent 命令携带的参数  
* @param instrumentation:提供操作类定义的相关方法  
*/  
public static void premain(String agentArgs, Instrumentation instrumentation) {  
    System.out.println(PerfMonitorAgent.class.getSimpleName() + ".premain was called.");  
    ClassFileTransformer trans = new ClassFileTransformer() {  
        @Override  
        public byte[] transform(ClassLoader loader,  
                                String className,  
                                Class<?> classBeingRedefined,  
                                ProtectionDomain protectionDomain,  
                                byte[] classfileBuffer) throws IllegalClassFormatException {  
            System.out.println("loader:" + loader.toString());  
            System.out.println("className:" + className);  
            System.out.println("classBeingRedefined:" + classBeingRedefined.getClass().getName());  
            
            return new byte[0];  
        }  
        };  
    System.out.println("Adding a ClassFileTransformer instance to the JVM.");  
    instrumentation.addTransformer(trans);  
    }  
}

测试类

运行时需要通过 -javaagent:/myAgent.jar 将打包后的agent包注入到项目里面

import java.util.concurrent.TimeUnit;  

public class Application {  
  
public static void main(String[] args) throws InterruptedException {  
    System.out.println("main 函数 运行了 ");  
    BirdService service = new BirdService();  
    service.jiji();  
    service.gugu();  
    }  
}  
  
class BirdService {  
    public void jiji() throws InterruptedException {  
        System.out.println("ji ji ------------");  
        TimeUnit.SECONDS.sleep(5);  
    }  

    public void gugu() throws InterruptedException {  
        System.out.println("gu gu ------------");  
        TimeUnit.SECONDS.sleep(5);  
    }  
}

maven配置

<plugin>  
    <groupId>org.apache.maven.plugins</groupId>  
    <artifactId>maven-assembly-plugin</artifactId>  
    <version>3.4.2</version>  
    <configuration>  
        <archive>  
            <manifestEntries>  
                <Premain-Class>org.example.agent.PerfMonitorAgent</Premain-Class>  
                <Agent-Class>org.example.agent.PerfMonitorAgent</Agent-Class>  
                <Can-Redefine-Classes>true</Can-Redefine-Classes>  
                <Can-Retransform-Classes>true</Can-Retransform-Classes>  
            </manifestEntries>  
        </archive>  
        <descriptorRefs>  
            <descriptorRef>jar-with-dependencies</descriptorRef>  
        </descriptorRefs>  
        <appendAssemblyId>false</appendAssemblyId>  
        <finalName>myAgent</finalName>  
    </configuration>  
    <executions>  
        <execution>  
            <id>make-assembly</id>  
            <phase>package</phase>  
            <goals>  
                <goal>single</goal>  
            </goals>  
        </execution>  
    </executions>  
</plugin>

运行效果

PerfMonitorAgent.premain was called.
Adding a ClassFileTransformer instance to the JVM.
loader:sun.misc.Launcher$AppClassLoader@18b4aac2
className:com/intellij/rt/execution/application/AppMainV2$Agent
loader:sun.misc.Launcher$AppClassLoader@18b4aac2
className:com/intellij/rt/execution/application/AppMainV2
loader:sun.misc.Launcher$AppClassLoader@18b4aac2
className:com/intellij/rt/execution/application/AppMainV2$1
loader:sun.misc.Launcher$AppClassLoader@18b4aac2
className:com/example/one/Application
main 函数 运行了 
loader:sun.misc.Launcher$AppClassLoader@18b4aac2
className:com/example/one/BirdService
ji ji ------------
gu gu ------------

深入

Instrumentation

JDK 1.5 版本开始新增了Instrumentation。 java.lang.instrument.Instrumentation接口类提供了检测Java编程语言代码所需的服务。插桩是将字节码添加到方法中,目的是收集供工具使用的数据。由于这些更改纯粹是附加的,所以这些工具不会修改应用程序的状态或行为。此类良性工具的示例包括监视代理、分析程序、覆盖率分析程序和事件记录程序。

有两种方法可以获得Instrumentation接口的实例:

  1. 当 JVM 以指示代理类的方式启动时。在这种情况下,Instrumentation实例被传递给代理类的premain方法。
  2. JVM提供了在JVM启动后启动代理的机制。在这种情况下,Instrumentation实例被传递给代理代码的agentmain方法。

一旦代理获得了Instrumentation实例,代理就可以随时调用实例上的方法。

在 JDK 中 Instrumentation只有一个实现 sun.instrument.InstrumentationImpl

premain

基本概念

  • 加载时机:JVM虚机运行时加载
  • MANIFEST.MF 文件中需要有 Premain-Class: 信息

典型应用

分布式系统性能检测工具 skywalking

agentmain

基本概念

  • 加载时机:JVM虚机已启动后,使用 attach(String pid) 的方式,对应JDK方法
    • VirtualMachine.attach(String id)
    • VirtualMachine.attach(VirtualMachineDescriptor vmd)

典型应用

阿里的监控诊断产品 Arthas 同时实现了 premain 和 agentmain。对于监控诊断工具而言,agentmain 已经够用。