一篇文章带你揭开 Java Instrumentation 的原理

4 篇文章 0 订阅
2 篇文章 0 订阅

一、前言

日常开发中避免不了,修改了代码重新启动应用去验证问题,如果没有热部署,则需要每次修改完就去编译代码再启动,这样子的操作看似简单,
但很耗时,特别电脑配置不高,或者项目比较大的情况

二、热部署初识

热部署其实就是在代码运行时去加载我们动态现在修改过的代码到服务器上,诸如 SpringBoot的devtools插件,jrebel插件等等,都是热部署的插件

三、Java Instrumentation

自Jdk5开始,就引入了 Java Instrumentation,它可以通过 addTransformer 方法设置一个 ClassFileTransformer,可以在这个 
ClassFileTransformer 实现类的转换

jdk5提供的Instrumentation 是静态的,基本思路就是在java程序启动前去加载一个代理(javaagent),这个javaagent是一个jar,然后需要编写
一个premain() 方法,然后记录在MANIFEST.MF中,在运行main()方法前,会先运行premain()方法里的逻辑
整个代理的过程基本是:先将代理类打成一个jar,然后在主程序中加上 -javaagent 的参数,参数的值是代理jar的全路径

四、Java Instrumentation 静态代码示例

4.1、编写permain()方法(示例):

import java.lang.instrument.Instrumentation;

/**
 * @author LGY
 * @date 2021/10/25 22:46
 */
public class TestAgent {


    public static void premain(String agentArgs, Instrumentation instrumentation){
        System.out.println("agent start");

        System.out.println(agentArgs);

    }

4.2、在MANIFEST.MF中指定premain()的路径

Manifest-Version: 1.0
Premain-Class: com.lgydojava.jvmdemo.agent.TestAgent

4.3、在maven中加入如下插件,在maven中指定Premain-Class的目的是:在maven将程序打成jar时,会替换掉MANIFEST.MF中的内容,所以这样要指定Premain-Class的值

<build>
  <finalName>my-javaagent</finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifestEntries>
            <Premain-Class>com.lgydojava.jvmdemo.agent.TestAgent</Premain-Class>
            <Can-Redefine-Classes>true</Can-Redefine-Classes>
            <Can-Retransform-Classes>true</Can-Retransform-Classes>
          </manifestEntries>
        </archive>
      </configuration>
    </plugin>
  </plugins>
</build>

4.4、在主程序中加入 -javaagent 启动参数
在这里插入图片描述

4.5、启动主程序 main()方法
在这里插入图片描述

五、Java Agent 示例 —— attach的使用

Java Instrumentation 动态加载被修改的代码 —— attach
业内很出名的arthas 也是利用了attach的原理来实现的

5.1、修改pom文件

<dependencies>

		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm</artifactId>
			<version>9.1</version>
		</dependency>

		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm-commons</artifactId>
			<version>8.0</version>
		</dependency>


	</dependencies>

	<build>
		<finalName>my-attach-agent-project</finalName>
		<plugins>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<version>3.2.0</version>
				<configuration>
					<archive>

						<manifestEntries>
							<Agent-Class>com.lgydojava.AgentMain</Agent-Class>
<!--							<Premain-Class>AgentMain</Premain-Class>-->
							<Can-Redefine-Classes>true</Can-Redefine-Classes>
							<Can-Retransform-Classes>true</Can-Retransform-Classes>
						</manifestEntries>
					</archive>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<version>3.2.4</version>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
						<configuration>
							<relocations>
								<relocation>
									<pattern>org.ow2.asm</pattern>
									<shadedPattern>me.ya.agent.hidden.org.objectweb.asm</shadedPattern>
								</relocation>
							</relocations>
						</configuration>
					</execution>
				</executions>

			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.7.0</version>
				<configuration>
					<source>8</source>
					<target>8</target>
				</configuration>

			</plugin>

		</plugins>

5.2、创建AgentMain类,实现在每个函数进入和结束时都打印一行日志,实现调用过程的追踪的效果


import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;

import static org.objectweb.asm.Opcodes.ASM7;

/**
 * @author LGY
 * @date 2021/10/26 23:03
 */
public class AgentMain {


    public static class MyMethodVisitor extends AdviceAdapter {

        protected MyMethodVisitor( MethodVisitor methodVisitor,int access, String name, String desc) {
            super(ASM7,methodVisitor,access, name, desc);
        }

        @Override
        protected void onMethodEnter(){
//            mv.
            mv.visitIntInsn(BIPUSH,50);
            mv.visitInsn(IRETURN);
        }
    }

    public static class MyClassVisitor extends ClassVisitor {
        public MyClassVisitor(ClassVisitor classVisitor) {
            super(ASM7, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){

            MethodVisitor mv = super.visitMethod(access,name,descriptor,signature,exceptions);
            System.out.println(name);
            if ("foo".equalsIgnoreCase(name)) {
                return new MyMethodVisitor(mv,access,name,descriptor);
            }

            return mv;
        }
    }

    public static class MyClassFileTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            System.out.println("className:"+className);
//            if (!"com.lgydojava.jvmdemo.MyTestMain".equalsIgnoreCase(className)) {
//                return classfileBuffer;
//            }

            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new MyClassVisitor(cw);
            cr.accept(cv,ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
    }

    public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
        System.out.println("agentmain called");
        inst.addTransformer(new MyClassFileTransformer(),true);
        Class classes[] = inst.getAllLoadedClasses();

        for (int i = 0; i < classes.length; i++) {
//            inst.retransformClasses(classes[i]);
            Class aClass = classes[i];
            String name = aClass.getName();
            System.out.println(name);
            if (name.equals("com.lgydojava.jvmdemo.MyTestMain")) {
                System.out.println("Reloading: " + name);
                inst.retransformClasses(aClass);
                break;
            }
        }


    }


}

5.5、 在MANIFEST.MF中指定Agent类

Manifest-Version: 1.0
Can-Redefine-Classes: true
Agent-Class: com.lgydojava.AgentMain
Can-Retransform-Classes: true
Permissions: all-permissions

5.6、 将程序打成jar包

5.7、 创建 attach类 MyAttachMain.java,PID是运行程序的ID,window可以在任务管理器中查看,loadAgent是agent.jar的全路径

import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

/**
 * @author LGY
 * @date 2021/10/26 23:31
 */
public class MyAttachMain {


    public static void main(String[] args) throws IOException, AttachNotSupportedException {
        VirtualMachine vm = VirtualMachine.attach(PID);
        try {
            vm.loadAgent("E:\\Idea_workspace\\jvmdemo\\target\\my-attach-agent-project.jar");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            vm.detach();
        }
    }


}

5.8、编写MyTestMain 主程序,并运行起来

import java.util.concurrent.TimeUnit;

/**
 * @author LGY
 * @date 2021/10/27 22:39
 */
public class MyTestMain {

    public static void main(String[] args) throws InterruptedException {
        while (true){
            System.out.println(foo());
            TimeUnit.SECONDS.sleep(3);
        }
    }

    private static int foo() {
    	//将原来返回100的,改为返回50
        return 100;
    }
}

5.9、运行验证

 输入MyTestMain 的PID,然后运行 MyAttachMain ,查看结果,可以看到原来是返回100的,当我们修改后,就返回了50

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊狸的Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值