Java动态编程之Instrumentation

需求

在不修改代码重新发布的情况下,查看一个线上正运行代码的方法的入参、返回值,或者增加某些日志的打印

解决方案

  • arthas
  • btrace
  • 自定义classloader,发现需要动态加载的类定义变更时对类进行重新加载打破双亲委派

本文暂不讨论自定义classloader方式,工具的具体使用方法可以查看官方文档,那么这些工具的底层是如何实现的呢?没错他们底层都使用了JDK提供的Instrumentation接口。那么Instrumentation接口如何使用呢?

用法

以下内容为翻译的该接口的javadoc 该类提供测试Java编程语言代码所需的服务。Instrumentation是将字节码添加至方法中为收集工具所使用的数据。因为改变是纯添加动作,这些工具不会改变应用状态或行为。像这样良性的工具案例包含监控代理agents,profilers,coverage analyzers, and event loggers. 获取该接口实例的方法有两种:

  1. 当JVM通过显示的指定一个代理类的方式运行。在这种情形下,Instrumentation实例通过代理类的premain方法传入
  2. JVM提供一个机制在JVM运行之后启动代理。在这种情形下,Instrumentation实例通过代理代码的agentmain方法传入

翻译结束23333

显示指定代理类方式获取

案例代码取自arthas pom配置为生成的manifest文件中指定premain代码提供类以及代理类提供类

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>single</goal>
            </goals>
            <phase>package</phase>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifestEntries>
                        <Premain-Class>com.taobao.arthas.agent.AgentBootstrap</Premain-Class>
                        <Agent-Class>com.taobao.arthas.agent.AgentBootstrap</Agent-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        <Specification-Title>${project.name}</Specification-Title>
                        <Specification-Version>${project.version}</Specification-Version>
                        <Implementation-Title>${project.name}</Implementation-Title>
                        <Implementation-Version>${project.version}</Implementation-Version>
                    </manifestEntries>
                </archive>
            </configuration>
        </execution>
    </executions>
</plugin>
复制代码

代理类、premain方法提供类编写接收Instrumentation实例的方法

// 通过-agent方法显示指定方式启动JVM时会回调该方法传入Instrumentation实例
public static void premain(String args, Instrumentation inst) {
    main(args, inst);
}
复制代码

将premain提供类打成jar包。启动其他jvm时将该该jar作为agent参数出入即可,启动命令如下:

java -javaagent "${HOME}/.arthas/lib/3.1.0/arthas/arthas-agent.jar" ...
复制代码

JVM启动后再启动代理方式获取

同上中方式相同,maven的pom配置为生成的manifest文件中指定agent代码提供类 代理类方法提供类编写接收Instrumentation实例的方法

// JVM启动后,通过VM.attach方式附加至目标JVM时回调该方法传入Instrumentation
public static void agentmain(String args, Instrumentation inst) {
    main(args, inst);
}
复制代码

将代理类附加到目标JVM

private void attachAgent(Configure configure) throws Exception {
    // 根据PID获取目标JVM描述实例
    VirtualMachineDescriptor virtualMachineDescriptor = null;
    for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) {
        String pid = descriptor.id();
        if (pid.equals(Integer.toString(configure.getJavaPid()))) {
            virtualMachineDescriptor = descriptor;
        }
    }
    VirtualMachine virtualMachine = null;
    try {
        // 通过pid方式直接依附至目标JVM
        if (null == virtualMachineDescriptor) { // 使用 attach(String pid) 这种方式
            virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
        } else {
            // 通过JVM描述符方式依附
            virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
        }
    ...
        // 为依附上的目标JVM加载代理jar
        virtualMachine.loadAgent(arthasAgentPath,
                configure.getArthasCore() + ";" + configure.toString());
    ...
}
复制代码

通过Instrumentation转换类字节码

实现ClassFileTransformer接口的transform方法

@Override
public byte[] transform(final ClassLoader inClassLoader, String className, Class<?> classBeingRedefined,            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        ...
		// inClassLoader:将要被转换类所在的类加载器,如果是bootstrap loader则该参数为null
		// className:将要被转换类的全限定名称
		// classBeingRedefined:将要被转换类的class
		// protectionDomain:将要被转换类的保护域
		// classfileBuffer:将要被转换类的字节码
		// 修改增强后的字节码
        return enhanceClassByteArray;
        ...
}
复制代码

将ClassFileTransformer实现添加至Instrumentation,并执行转换

// Enhancer为arthas中提供的ClassFileTransformer实现类
final Enhancer enhancer = new Enhancer(adviceId, isTracing, skipJDKTrace, enhanceClassSet, methodNameMatcher, affect);
// 将转换类添加至Instrumentation
inst.addTransformer(enhancer, true);
...
// 执行转换
inst.retransformClasses(clazz);
复制代码

总结

  • instrumentation.addTransformer的第二个参数canRetransform如果不指定默认为false,如果为true,则在显示调用retransformClasses时会立即回调。
  • jdk api文档中指明在转换类定义之前已经实例化的对象不会受影响,这种说法指的应该是因为对象不能新增、删除、更改字段方法等,所以类型的实例不会受到重定义的影响;但是对于方法的修改会生效,官方也有说明:已经入栈的方法不会受影响,但是对于下一次方法调用则会使用新的定义
  • ClassFileTransformer实例中要对类名称进行匹配,因为其他类加载时也可能会走到该转换类中导致出现异常,例如:
###### start transform
loader:sun.misc.Launcher$AppClassLoader@18b4aac2, className:null, classBeingRedefined:null
###### end transform
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Agent是Arthas使用的技术,是Skywalking使用的技术,是一份十分重要的技术。 课程的稀缺性在此之前,市面上并没有针对Java Agent进行系统介绍的课程。 通过搜索引擎查找,会发现与Java Agent相关的内容大多是个人知识总结分享的内容。这些内容有如下特点:内容质量不一详略程度不一学习难度千差万别总体上来说,学习者很难有一个整体认知、系统学习的过程。 课程的设计目标 在构思课程内容时,本课程带有以下目标:课程学习梯度:从简单到复杂,让学习者有一个循序渐进的理解过程。构造完整、统一的知识体系:不是零散的知识点堆砌,而是有一个统一的贯穿始终的知识框架。具有可操作性的代码示例,不只是讲概念,更注意于实践。课程内容安排 本课程通过四章内容对Java Agent相关知识进行讲解:第一章,介绍Agent Jar的三个组成部分:Manifest、Agent Class和ClassFileTransformer。第二章,介绍Agent Jar的两种启动方式:从命令行启动和使用Attach机制启动。第三章,介绍如何利用Instrumentation API来实现Agent Jar的功能。第四章,Java Agent的应用与技巧。 通过本课程的学习,让同学们更好地建立起一个完整的知识体系:  讲师介绍我叫刘森,南京师范大学研究生毕业,2015年获得信息系统项目管理师(高级),2014年获得系统集成项目管理工程师(中级)。 目前,我的课程都是围绕着“Java字节码”技术展开: 《Java Agent基础篇》是在一个运行JVM当中提供修改字节码的机会《Java ASM系列》(免费课程)是一个操作字节码的类库《Java 8 ClassFile》专注于字节码的理论知识,入选为“51CTO数字化人才证书项目认证课程” 因此,我对字节码技术有较为深入的研究和理解,大家想学习字节码的技术可以找我:字节码技术找刘森,轻松学习又省心~~~ 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值