栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

文章目录
  • 一、前言
  • 二、热部署初识
  • 三、Java Instrumentation
  • 四、Java Instrumentation 静态代码示例
  • 五、Java Agent 示例 —— attach的使用

一、前言
日常开发中避免不了,修改了代码重新启动应用去验证问题,如果没有热部署,则需要每次修改完就去编译代码再启动,这样子的操作看似简单,
但很耗时,特别电脑配置不高,或者项目比较大的情况
二、热部署初识
热部署其实就是在代码运行时去加载我们动态现在修改过的代码到服务器上,诸如 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;


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的值


  my-javaagent
  
    
      org.apache.maven.plugins
      maven-jar-plugin
      
        
          
            com.lgydojava.jvmdemo.agent.TestAgent
            true
            true
          
        
      
    
  

4.4、在主程序中加入 -javaagent 启动参数

4.5、启动主程序 main()方法

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

5.1、修改pom文件



		
			org.ow2.asm
			asm
			9.1
		

		
			org.ow2.asm
			asm-commons
			8.0
		


	

	
		my-attach-agent-project
		

			
				org.apache.maven.plugins
				maven-jar-plugin
				3.2.0
				
					

						
							com.lgydojava.AgentMain

							true
							true
						
					
				
			
			
				org.apache.maven.plugins
				maven-shade-plugin
				3.2.4
				
					
						package
						
							shade
						
						
							
								
									org.ow2.asm
									me.ya.agent.hidden.org.objectweb.asm
								
							
						
					
				

			
			
				org.apache.maven.plugins
				maven-compiler-plugin
				3.7.0
				
					8
					8
				

			

		

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;


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;


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;


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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/353475.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号