JDK1.6"新"特性Instrumentation之JavaAgent

文章目录

  • JDK1.6"新"特性Instrumentation之JavaAgent
    • 简介
    • JavaAgent的作用
      • Agent给我们程序带来的影响.jpg
      • 使用Agent-premain方法影响的程序效果图.jpg
      • 使用Agent-agentmain方法影响的程序效果图.jpg
    • JavaAgent相关的API
    • JavaAgent-premain方法1-初探
      • 效果:
        • Agent1.java
        • Demo1.java
        • resources/META-INF/MANIFEST.MF
        • 运行效果
    • JavaAgent-premain方法2-实现修改代码逻辑
      • 效果:
        • Agent2.java
        • Demo2.java
        • resources/META-INF/MANIFEST.MF
        • 运行效果
    • JavaAgent-premain方法3-无侵入动态修改程序源代码实现方法耗时统计
      • 效果:
        • Agent3.java
        • Demo3.java
        • resources/META-INF/MANIFEST.MF
    • JavaAgent-agentmain方法1-实现运行时修改程序
      • 效果:
        • Agent4.java
        • resources/META-INF/MANIFEST.MF
    • JavaAgent-agentmain方法2-实现动态修改日志级别
      • 效果:
        • Agent5.java
        • resources/META-INF/MANIFEST.MF
    • 自己实现一个热部署功能的大致思路
      • 那么热部署具体应该怎么实现呢?
      • 思路
    • JavaAgent的应用场景
      • 源代码

简介

Java Agent是在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。

Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供)。

JavaAgent的作用

Agent给我们程序带来的影响.jpg

使用Agent-premain方法影响的程序效果图.jpg

使用Agent-agentmain方法影响的程序效果图.jpg

JavaAgent相关的API

在java.lang.instrument包下 给我们提供了相关的API

而最为主要的就是Instrumentation这个接口中的几个方法

public interface Instrumentation {/*** 添加Transformer(转换器) * ClassFileTransformer类是一个接口,通常用户只需实现这个接口的  byte[] transform()方法即可;* transform这个方法会返回一个已经转换过的对象的byte[]数组* @param transformer            拦截器* @return canRetransform        是否能重新转换*/void addTransformer(ClassFileTransformer transformer, boolean canRetransform);    /*** 重新触发类加载,* 该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也不能修改方法的签名* @param classes           Class对象* @throws  UnmodifiableClassException       异常*/void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;/*** 直接替换类的定义* 重新转换某个对象,并已一个新的class格式,进行转化。* 该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也不能修改方法的签名* @param definitions           ClassDefinition对象[Class定义对象]* @throws  ClassNotFoundException,UnmodifiableClassException       异常*/void redefineClasses(ClassDefinition... definitions)throws  ClassNotFoundException, UnmodifiableClassException;/*** 获取当前被JVM加载的所有类对象* @return Class[]        class数组*/Class[] getAllLoadedClasses();
}

后面我们会在代码中具体用到这些方法。再详细说明。

JavaAgent-premain方法1-初探

效果:

实现main方法前执行业务逻辑

Agent1.java

public class Agent1 {public static void premain(String agent){System.out.println("Agent1 premain :" + agent);}
}

Demo1.java

public class Demo1 {/*** VM参数* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input* */public static void main(String[] args) throws Exception {System.out.println("demo1");}
}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Premain-Class: cn.bigfire.Agent1
Can-Retransform-Classes: true

运行效果

Agent1 premain :input
demo1

JavaAgent-premain方法2-实现修改代码逻辑

效果:

实现 修改 程序源代码 hello -> hello agented

Agent2.java

public class Agent2 {/*** 可以运行在main方法启动前* @param agent             输入的参数* @param instrumentation             输入的参数*/public static void premain(String agent, Instrumentation instrumentation){System.out.println("Agent2 premain 2param :" + agent);instrumentation.addTransformer(new ConsoleTransformer(),true);}}

ConsoleTransformer.java

public class ConsoleTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if (className.equals("cn/bigfire/Console")){String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);String classFile = root + "JavaAgentDemo/agent/src/main/resources/Console.class";return FileUtil.readBytes(classFile);}return classfileBuffer;}
}

Demo2.java

public class Demo2 {/*** VM参数* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input* */public static void main(String[] args) throws Exception {new Thread(()->{while (true){Console.hello();//    public static void hello(){System.out.println("hello"); }ThreadUtil.sleep(2000);}}).start();}
}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Premain-Class: cn.bigfire.Agent2
Can-Retransform-Classes: true

运行效果

Agent2 premain 2param :input
满足条件
hello  agented
hello  agented
hello  agented
hello  agented

JavaAgent-premain方法3-无侵入动态修改程序源代码实现方法耗时统计

效果:

实现main方法外的所有方法统计时间

Agent3.java

public class Agent3 {/*** 可以运行在main方法启动前* @param agent                         输入的参数* @param instrumentation               instrumentation对象由JVM提供并传入*/public static void premain(String agent, Instrumentation instrumentation) {System.out.println("Agent3 premain :" + agent);instrumentation.addTransformer(new TimeCountTransformer());}/*** 时间统计Transformer  给要代理的方法添加时间统计*/private static class TimeCountTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {try {className = className.replace("/", ".");if (className.equals("cn.bigfire.Demo3")) {//使用全称,用于取得字节码类<使用javassist>CtClass ctclass = ClassPool.getDefault().get(className);//获得方法列表CtMethod[] methods = ctclass.getDeclaredMethods();//给方法设置代理Stream.of(methods).forEach(method-> agentMethod(ctclass,method));//CtClass转byte[]数组return ctclass.toBytecode();}} catch (Exception e) {e.printStackTrace();}return null;}}/*** 代理方法,把传入的方法经写代理,并生成带时间统计的方法,* @param ctClass                         javassist的Class类* @param ctMethod                        javassist的ctMethod方法* */public static void agentMethod(CtClass ctClass,CtMethod ctMethod){try {String mName = ctMethod.getName();if (!mName.equals("main")){//代理除了main方法以外的所有方法String newName = mName + "$Agent";ctMethod.setName(newName);CtMethod newMethod = CtNewMethod.copy(ctMethod, mName, ctClass, null);// 构建新的方法体String bodyStr = "{\n" +"long startTime = System.currentTimeMillis();\n" +newName + "();\n" +"long endTime = System.currentTimeMillis();\n" +"System.out.println(\""+newName+"() cost:\" +(endTime - startTime));\n" +"}";newMethod.setBody(bodyStr);// 替换新方法ctClass.addMethod(newMethod);// 增加新方法}}catch (Exception e){e.printStackTrace();}}}

Demo3.java

public class Demo3 {/*** VM参数* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input*/public static void main(String[] args) throws Exception {sleep1();sleep2();}public static void sleep1(){ThreadUtil.sleep(1000);}public static void sleep2(){ThreadUtil.sleep(2000);}}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Class-Path: ../javassist-3.12.1.GA.jar
Premain-Class: cn.bigfire.Agent3
Can-Retransform-Classes: true

运行效果

Agent3 premain :input
sleep1$Agent() cost:1005
sleep2$Agent() cost:2001

JavaAgent-agentmain方法1-实现运行时修改程序

效果:

实现运行时 修改程序 hello -> hello agented

Agent4.java

public class Agent4 {public static void premain(String agent){System.out.println("Agent4 premain 1param:" + agent);}public static void premain(String agent, Instrumentation instrumentation) {System.out.println("Agent4 premain 2param:" + agent);//premain时,由于堆里还没有相应的Class。所以直接addTransformer,程序就会生效。
//        instrumentation.addTransformer(new ConsoleTransformer(),true);}public static void agentmain(String agent, Instrumentation instrumentation){System.out.println("Agent4 agentmain 2param :" + agent);instrumentation.addTransformer(new ConsoleTransformer(),true);//agentmain运行时 由于堆里已经存在Class文件,所以新添加Transformer后// 还要再调用一个  inst.retransformClasses(clazz); 方法来更新Class文件for (Class clazz:instrumentation.getAllLoadedClasses()) {if (clazz.getName().contains("cn.bigfire.Console")){try {instrumentation.retransformClasses(clazz);} catch (Exception e) {e.printStackTrace();}}}}public static void agentmain(String agent){System.out.println("Agent4 agentmain 1param :" + agent);}}

Demo4

public class Demo4 {/*** 打包agent4 -> 先运行demo2 -> 运行demo4 ->选择程序demo2结尾的程序,即可运行时修改文件* VM参数* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input* */public static void main(String[] args) throws Exception {while (true){List<VirtualMachineDescriptor> list = VirtualMachine.list();for (int i = 0; i < list.size(); i++) {VirtualMachineDescriptor jvm = list.get(i);;System.out.println("[" +i+ "]ID:"+jvm.id()+",Name:"+jvm.displayName());}System.out.println("请选择第几个");Scanner scanner = new Scanner(System.in);int s = scanner.nextInt();VirtualMachineDescriptor virtualMachineDescriptor = list.get(s);VirtualMachine attach = VirtualMachine.attach(virtualMachineDescriptor.id());String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);String agentJar = root + "JavaAgentDemo\\agent\\target\\agent.jar";File file = new File(agentJar);System.out.println(file.exists());attach.loadAgent(agentJar,"param");attach.detach();}}
}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Premain-Class: cn.bigfire.Agent4
Agent-Class: cn.bigfire.Agent4
Can-Retransform-Classes: true
Can-Redefine-Classes: true

此时的运行顺序
打包agent4 -> 先运行demo2 -> 运行demo4 ->选择程序demo2结尾的程序,即可运行时修改文件

运行效果
Demo2

Agent4 premain 2param:input
hello
hello

Demo4

[0]ID:12480,Name:cn.bigfire.Demo2
[1]ID:14832,Name:org.jetbrains.kotlin.daemon.KotlinCompileDaemon --daemon-runFilesPath xxx
[2]ID:14864,Name:
[3]ID:3952,Name:cn.bigfire.Demo4
[4]ID:14852,Name:org.jetbrains.idea.maven.server.RemoteMavenServer36
[5]ID:11928,Name:org.jetbrains.jps.cmdline.Launcher xxx
请选择第几个
0
true

Demo2

Agent4 premain 2param:input
hello
hello
Agent4 agentmain 2param :param
hello  agented
hello  agented
hello  agented

JavaAgent-agentmain方法2-实现动态修改日志级别

效果:

实现运行时 修改程序 模拟项目中的动态日志 info <-> debug

Agent5.java

public class Agent5 {public static void premain(String agent, Instrumentation instrumentation){System.out.println("Agent5 premain 2param :" + agent);instrumentation.addTransformer(new StartTransformer(),true);//这个方式不行。因为启动时Class都还没有呢。
//        for (Class clazz:inst.getAllLoadedClasses()) {//            if (clazz.getName().equals("cn.bigfire.LogLevelStarter")){//                try {//                    switchDebug(clazz);
//                    instrumentation.retransformClasses(clazz);
//                } catch (Exception e) {//                    e.printStackTrace();
//                }
//            }
//        }}public static void agentmain(String agent, Instrumentation instrumentation){System.out.println("Agent5 agentmain 2param :" + agent);for (Class clazz:instrumentation.getAllLoadedClasses()) {if (clazz.getName().equals("cn.bigfire.LogLevelStarter")){try {switchAtomicDebug(clazz);instrumentation.retransformClasses(clazz);} catch (Exception e) {e.printStackTrace();}}}}public static class StartTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {//此时由于classBeingRedefined是空,所以还是不能用这个Class修改属性呢,只能通过 读取byte[]往堆里丢,才能用。if (className.equals("cn/bigfire/LogLevelStarter")){//【这是一个错误的思路】 premain的时候  classBeingRedefined是空的因为很多的Class还没加载到堆中
//                if (classBeingRedefined!=null){//                    switchDebug(classBeingRedefined);
//                    return toBytes(classBeingRedefined);
//                }//正常的读取一共文件byte[]数组String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);String classFile = root + "JavaAgentDemo/agent/src/main/resources/LogLevelStarter.class";return FileUtil.readBytes(classFile);}return classfileBuffer;}}/*** 可序列化对象转byte[]数组* @param clazz             要转byte[]数组的对象* @return byte[]           返回byte[]数组*/public static byte[] toBytes(Serializable clazz){try {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);stream.writeObject(clazz);return byteArrayOutputStream.toByteArray();}catch (Exception e){e.printStackTrace();}return null;}public static void switchDebug(Class clazz){try {Field field1 = clazz.getDeclaredField("isDebug");field1.setAccessible(true);boolean debug = field1.getBoolean(clazz);field1.setBoolean(clazz,!debug);}catch (Exception e){e.printStackTrace();}}public static void switchAtomicDebug(Class clazz){try {Field field2 = clazz.getDeclaredField("atomicDebug");field2.setAccessible(true);AtomicBoolean atomicDebug = (AtomicBoolean)field2.get(clazz);atomicDebug.set(!atomicDebug.get());}catch (Exception e){e.printStackTrace();}}}

注意,需要先把LogLevelStarter.java中的isDebug 改为true编译一下。放到src/main/resources/目录下;

LogLevelStarter.java

public class LogLevelStarter {public static volatile boolean isDebug = false;public static AtomicBoolean atomicDebug = new AtomicBoolean(false);/*** VM参数* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input*/public static void main(String[] args) throws Exception {new Thread(()->{for (;;){//死循环,每隔两秒打印一个日志。System.out.print(isDebug ? "volatile debug" : "volatile info");System.out.print("\t");System.out.println(atomicDebug.get() ? "atomicDebug debug" : "atomicDebug info");ThreadUtil.sleep(2000);}}).start();}
}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Premain-Class: cn.bigfire.Agent5
Agent-Class: cn.bigfire.Agent5
Can-Retransform-Classes: true
Can-Redefine-Classes: true

此时的运行顺序
打包agent5 -> 先运行LogLevelStarter -> 运行demo4 ->选择程序LogLevelStarter结尾的程序,即可运行时修改文件

运行效果

LogLevelStarter

Agent5 premain 2param :input
volatile debug  atomicDebug info
volatile debug  atomicDebug info

Demo4

[0]ID:12592,Name:cn.bigfire.LogLevelStarter
[1]ID:12880,Name:cn.bigfire.Demo4
[2]ID:14832,Name:org.jetbrains.kotlin.daemon.KotlinCompileDaemon --daemon-runFilesPath xxx
[3]ID:14864,Name:
[4]ID:14852,Name:org.jetbrains.idea.maven.server.RemoteMavenServer36
[5]ID:8116,Name:org.jetbrains.jps.cmdline.Launcher xxx
请选择第几个
0
true

LogLevelStarter

Agent5 premain 2param :input
volatile debug  atomicDebug info
volatile debug  atomicDebug info
Agent5 agentmain 2param :param
volatile debug  atomicDebug debug
volatile debug  atomicDebug debug

在Agent5中,其实使用premain和agentmain。

premain把volatile修饰的isDbug给修改为true了。

而agentmain时把atomicDebug的值进行多次取反操作。

自己实现一个热部署功能的大致思路

当运行完本项目中的几个demo之后。

读者可能对Java Agent有了一些基本的概念

最起码我们知道了premain是可以运行在main函数前的。

agentmain是可以在程序运行时,修改程序内的一些类文件的。

那么热部署很明显就是使用的agentmain这个特性了

那么热部署具体应该怎么实现呢?

这里先有个大概的思路。后续如果有经历,可以简单按照这个思路实现一下

思路

当我们文件发生修改的时候,项目会重新加载我们的类。

那么这里肯定会涉及到文件变化的观察。 即 观察者设计模式跑不了

首先递归当前项目目录。并根据文件类型,如(.java ,xml,yml等)将此类文件注册观察者模式。

当文件内容发生变化时,会调用 监听器中的回调方法;

在回调中完成如下(具体实现时未必需要)

使用Java1.6的JavaCompiler编译Java文件;
自定义ClassLoader 装载 编译好的Class到堆中

使用agentmain修改原Class文件替换成新的Class文件

完成热加载

JavaAgent的应用场景

apm:(Application Performance Management)应用性能管理。pinpoint、cat、skywalking等都基于Instrumentation实现
idea的HotSwap、Jrebel等热部署工具
应用级故障演练
Java诊断工具Arthas、Btrace等

源代码

{"author": "大火yzs","title": "【JavaAgent】JavaAgent入门教程","tag": "JavaAgent,Instrumentation,运行时动态修改源程序","createTime": "2020-08-02  18:30"
}

JDK1.6“新“特性Instrumentation之JavaAgent相关推荐

  1. Java SE 6 新特性 Instrumentation 新功能

    系列内容: 此内容是该系列的一部分:Java SE 6 新特性 Instrumentation 简介 利用 Java 代码,即 java.lang.instrument 做动态 Instrumenta ...

  2. JDK1.8 新特性(全)

    JDK1.8 新特性 本文主要介绍了JDK1.8版本中的一些新特性,乃作者视频观后笔记,仅供参考. jdk1.8新特性知识点: Lambda表达式 函数式接口 方法引用和构造器调用 Stream AP ...

  3. jdk1.8新特性的应用-Stream 的终止操作

    jdk1.8新特性的应用-Stream 的终止操作 public class TestStreamApi4 {List<Employee> emps = Arrays.asList(new ...

  4. jdk1.8新特性_Lambda表达式的引入

    jdk1.8新特性_Lambda表达式的引入 引入 需求: 获取工资大于20000的员工信息 public class Employee {private String name;private in ...

  5. jdk1.5新特性5之枚举之模拟枚举类型

    一 简单应用 package cn.xy.Enum; public enum TrafficLamp {  RED,GREEN,YELLOW; } TrafficLamp red = TrafficL ...

  6. java 1.7 可变参数,JDK1.7新特性(2):异常和可变长参数处理

    异常 jdk1.7对try--catch--finally的异常处理模式进行了增强,下面我们依次来看增强的方面. 1. 为了防止异常覆盖,给Throwable类增加了addSuppressed方法,可 ...

  7. JAVA day20、21 双列集合Map<K,V>:HashMap,LinkedHashMap,TreeMap,Hashtable, ConcurrentHashMap;JDK1.9新特性

    一.Map<K,V> Java提供了专⻔的集合类⽤来存放这种这种⼀⼀对应的关系,叫做映射对象,即 java.util.Map 接⼝. 类型参数: K - 此映射所维护的键的类型 V - 映 ...

  8. 黑马程序员————高新技术————JDK1.5新特性

    ----------------------ASP.Net+Android+IOS开发----------------------期待与您交流! JDK1.5新特性 一:静态导入 l  Import语 ...

  9. JDK-1.5_新特性

    1.泛型(Generics) 泛型是JDK1.5中一个最"酷"的特征.通过引入泛型,我们将获得编译时类型的安全和运行时更小地抛出ClassCastExceptions的可能.在JD ...

最新文章

  1. [日常] Go语言圣经-Deferred函数
  2. Python 抖音机器人,论如何在抖音上找到漂亮小姐姐?
  3. docker系列之file基本操作
  4. Ubuntu_Win10双系统互换注意事项以及蓝屏解决方案
  5. IIS问题解决之——无法访问数据库
  6. 2017年如何成为全栈工程师,这些技能是你必须具备的!
  7. C++菱形继承产生的问题和解决
  8. pomelo中的next
  9. 【java】System.getProperty()参数大全
  10. Qt中字符串之间的转换
  11. Redis在项目中的地位及使用场景剖析
  12. poj 3268 SliverrCowParty Dijkstra最短路(单向路转向)
  13. 有什么办法可以判断页面是静态还是动态?_你知道seo到底是什么吗?该怎么优化?...
  14. 单商户商城系统功能拆解13—分类管理
  15. KITTI数据集GPS数据格式dataformat
  16. python自动化中使用unittestreport输出测试报告
  17. 雷蛇鼠标 雷云3 驱动无法启动 Razer Syncapse 3 Failed to start
  18. 知识库管理-运维管理
  19. 长江大学计算机科学学院德贵奖学金,关于做好2019年各类奖学金评选的通知
  20. Three things can't discuss with with people: Religion, Politics and The Great Pumpkin. Why?为什么不能谈论?

热门文章

  1. 安装colmap时报错“METIS_INCLUDE_DIRS-NOTFOUND“
  2. Redis介绍--列表(LISTS)数据类型(六)
  3. 为什么苹果文件连接服务器没反应,苹果手机连接电脑没反应,教您苹果手机连接电脑没反应具体解决方法...
  4. 如何向icloud上传文件_怎么把文件放进苹果icloud
  5. Python之保留小数点后n位、向上取整、向下取整以及四舍五入函数
  6. 2022年全国职业院校技能大赛:网络系统管理项目 B模块-Windows部署(10套样题)
  7. 计算机专业名词TIME,date
  8. Nat. Rev. Genet. | 通过可解释人工智能从深度学习中获得遗传学见解
  9. 研华IO控制卡硬件接线方式
  10. Taro——taro安装及taro创建项目相关命令