技术经验分享:javaagent技术&Attach技术

简介: 技术经验分享:javaagent技术&Attach技术

  之前见过好多种-javaagent 参数,比如我们IDEA启动一个类的时候就会有好多的javaagent。 好像又叫探针技术,简单研究下其过程。


  Java 5 中提供的 Instrument 包启动时往 Java 虚拟机中挂上一个用户定义的 hook 程序,可以在装入特定类的时候改变特定类的字节码,从而改变该类的行为。Instrument 包是在整个虚拟机上挂了一个钩子程序,每次装入一个新类的时候,都必须执行一遍这段程序,即使这个类不需要改变。一个核心类是sun.instrument.InstrumentationImpl, 这个类可以动态的增加转换器或者获取当前JVM加载的所有的类信息。


  参考:


第一种: 使用 premain 可以在类第一次加载之前修改类信息,加载之后修改需要重新创建类加载器, premain是Java SE5开始就提供的代理方式。而且使用时必须在命令行指定代理jar,并且代理类必须在main方法前启动。


第二种: Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。


1. premain 使用


1. 简单使用


1. agent.MyAgent


package agent;


import java.lang.instrument.Instrumentation;


public class MyAgent {


/


JVM 在类加载前会调用到此函数



@param agentOps


@param inst


/


public static void premain(String agentOps, Instrumentation inst) {


System.out.println("agent.MyAgent.premain start ");


System.out.println(agentOps);


System.out.println(inst);


System.out.println("agent.MyAgent.premain end ");


}


}


2. 编写META-INF/MANIFEST.MF


Manifest-Version: 1.0


Can-Retransform-Classes: true


Premain-Class: agent.MyAgent


这个配置文件需要注意格式,如果之前打过jar 包应该会注意。 最后有个空行, 每个key后面的冒号 和 value 之间有个空格


3. 最后的目录结构如下:


$ ls -R


.:


agent/ META-INF/


./agent:


MyAgent.class


./META-INF:


MANIFEST.MF


4. 生成jar 包


D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./


已添加清单


正在添加: agent/(输入 = 0) (输出 = 0)(存储了 0%)


正在添加: agent/MyAgent.class(输入 = 754) (输出 = 415)(压缩了 44%)


正在忽略条目META-INF/


正在忽略条目META-INF/MANIFEST.MF


5. 新建测试类:


public class PlainTest {


public static void main(String【】 args) throws InterruptedException {


new PlainTest().test();


}


public void test() throws InterruptedException {


Thread.sleep(5 1000);


System.out.println("cn.qz.PlainTest.test\t" + 111222);


}


}


6. 编译运行测试:


$ java -javaagent:D:/agentjar/agent.jar PlainTest


agent.MyAgent.premain start


null


sun.instrument.InstrumentationImpl@5fe5c6f


agent.MyAgent.premain end


cn.qz.PlainTest.test 111222


测试传递参数: (可以传递参数给指定的方法, 方法内部也可以根据参数进行一些特殊的处理)


$ java -javaagent:D:/agentjar/agent.jar=key1=value1,key2=value2 PlainTest


agent.MyAgent.premain start


key1=value1,key2=value2


sun.instrument.InstrumentationImpl@5fe5c6f


agent.MyAgent.premain end


cn.qz.PlainTest.test 111222


7. 使用IDEA 的方式进行调试


  同样的jar 包指定使用之前打的jar,和探针对应的类可以在DIEA 中使用java 文件进行调试。


(1) 目录结构


(2) agent.MyAgent


package agent;


import java.lang.instrument.Instrumentation;


public class MyAgent {


/


JVM 在类加载前会调用到此函数



@param agentOps


@param inst


/


public static void premain(String agentOps, Instrumentation inst) {


System.out.println("agent.MyAgent.premain XXX start ");


System.out.println(agentOps);


System.out.println(inst);


System.out.println("agent.MyAgent.premain XXX end ");


}


}


(3) 增加测试类


package cn.qz;


public class PlainTest {


public static void main(String【】 args) throws InterruptedException {


new PlainTest().test();


}


public void test() throws InterruptedException {


Thread.sleep(5 1000);


System.out.println("cn.qz.PlainTest.test\t" + 111222);


}


}


(4) 编辑增加代理 idea 中 Add VM Operations 增加参数: -javaagent:D:\agentjar\agent.jar


(5) 测试查看运行结果:


agent.MyAgent.premain XXX start


null


sun.instrument.InstrumentationImpl@1eb44e46


agent.MyAgent.premain XXX end


cn.qz.PlainTest.test 111222


(6) debug 到agent.MyAgent#premain 方法内部, 查看调用链:


2. premain 实现监测方法执行时间的操作


  基于javassit 对字节码进行增强。


1. pom 增加


org.javassist


javassist


3.28.0-GA


2. agent.MyAgent 源码


package agent;


import javassist.ClassPool;


import javassist.CtClass;


import javassist.CtMethod;


import java.lang.instrument.ClassFileTransformer;


import java.lang.instrument.IllegalClassFormatException;


import java.lang.instrument.Instrumentation;


import java.security.ProtectionDomain;


public class MyAgent {


/


有该方法会优先执行该方法



@param agentOps


@param inst


*/


public static void premain(String agentOps, Instrumentation inst) {


System.out.println("====premain 方法执行");


System.out.println(agentOps);


System.out.println(inst);


System.out.println("====premain2 方法执行");


/


添加一个转换器, 字节码加载到虚拟机前会调用此类的transform 方法


/


inst.addTransformer(new ClassFileTransformer() {


@Override


public byte【】 transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte【】 classfileBuffer) throws IllegalClassFormatException {


if (!className.startsWith("cn/qz")) {


return null;


}


System.out.println("ClassLoader : " + loader);


System.out.println("className : " + className);


//创建类,这是一个单例对象


ClassPool cp = ClassPool.getDefault();


//我们需要构建的类


try {


CtClass ctClass = cp.get(className.replace("/", "."));


CtMethod【】 declaredMethods = ctClass.getDeclaredMethods();


for (CtMethod method : declaredMethods) {


// 修改方法体来实现, 增加两个局部变量用于记录执行时间


method.addLocalVariable("startTimeAgent", CtClass.longType);


method.insertBefore("startTimeAgent = System.currentTimeMillis();");


method.addLocalVariable("methodNameAgent", cp.get(String.class.getName()));


method.insertBefore("methodNameAgent = \""//代码效果参考:http://www.jhylw.com.cn/211138523.html

+ method.getLongName() + "\";");

method.insertAfter("System.out.println(methodNameAgent + \" exec time is :\" + (System.currentTimeMillis() - startTimeAgent) + \"ms\");");


}


return ctClass.toBytecode();


} catch (Exception e) {


e.printStackTrace();


}


return null;


}


});


}


}


3. 使用上面测试类进行测试查看日志


====premain 方法执行


null


sun.instrument.InstrumentationImpl@1eb44e46


====premain2 方法执行


ClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2


className : cn/qz/PlainTest


cn.qz.PlainTest.test 111222


cn.qz.PlainTest.test() exec time is :5001ms


cn.qz.PlainTest.main(java.lang.String【】) exec time is :5002ms


4. 打包:


(1) 下载pom 依赖的jar 包, 进入项目目录(有pom.xml的目录),cmd执行如下命令


mvn dependency:copy-dependencies -DoutputDirectory=dependency_lib


(2) 构造目录结构:


$ ls -R


.:


agent/ dependency_lib/ META-INF/


./agent:


'MyAgent$1.class' MyAgent.class


./dependency_lib:


javassist-3.28.0-GA.jar


./META-INF:


MANIFEST.MF


(3) 修改MANIFEST.MF


Manifest-Version: 1.0


Can-Retransform-Classes: true


Class-Path: dependency_lib/javassist-3.28.0-GA.jar


Premain-Class: agent.MyAgent


(4) 替换编译后的class 文件


(5) 打包


D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./


已添加清单


正在添加: agent/(输入 = 0) (输出 = 0)(存储了 0%)


正在添加: agent/MyAgent$1.class(输入 = 3142) (输出 = 1533)(压缩了 51%)


正在添加: agent/MyAgent.class(输入 = 941) (输出 = 524)(压缩了 44%)


正在添加: dependency_lib/(输入 = 0) (输出 = 0)(存储了 0%)


正在添加: dependency_lib/javassist-3.28.0-GA.jar(输入 = 851531) (输出 = 794719)(压缩了 6%)


正在忽略条目META-INF/


正在忽略条目META-INF/MANIFEST.MF


(6) 编写测试类编译后测试:


package cn.qz;


import java.util.concurrent.CountDownLatch;


public class PlainTest {


public static void main(String【】 args) throws InterruptedException {


new PlainTest().test();


CountDownLatch countDownLatch = new CountDownLatch(1);


countDownLatch.await();


}


public void test() throws InterruptedException {


Thread.sleep(5 1000);


System.out.println("cn.qz.PlainTest.test\t" + 111222);


}


}


1》测试:


D:\agentjar>java -javaagent:D:\agentjar\agent.jar cn.qz.PlainTest


====premain 方法执行


null


sun.instrument.InstrumentationImpl@6979e8cb


====premain2 方法执行


ClassLoader : jdk.internal.loader.ClassLoaders$AppClassLoader@383534aa


className : cn/qz/PlainTest


cn.qz.PlainTest.test 111222


cn.qz.PlainTest.test() exec time is :5001ms


2》 使用arthas 实时反编译查看生成的类信息


/


Decompiled with CFR.


/


package cn.qz;


import java.util.concurrent.CountDownLatch;


public class PlainTest {


/


WARNING - void declaration


/


public static void main(String【】 stringArray) throws InterruptedException {


void var2_1;


void var4_2;


long startTimeAgent = System.currentTimeMillis();


String methodNameAgent = "cn.qz.PlainTest.main(java.lang.String【】)";


new PlainTest().test();


CountDownLatch countDownLatch = new CountDownLatch(1);


/10/ countDownLatch.await();


Object var6_4 = null;


System.out.println(new StringBuffer().append((String)var4_2).append(" exec time is :").append(System.currentTimeMillis() - var2_1).append("ms").toString());


}


/


WARNING - void declaration


/


public void test() throws InterruptedException {


void var1_1;


void var3_2;


long startTimeAgent = System.currentTimeMillis();


String methodNameAgent = "cn.qz.PlainTest.test()";


/14/ Thread.sleep(5000L);


/15/ System.out.println("cn.qz.PlainTest.test\t111222");


Object var5_3 = null;


System.out.println(new StringBuffer().append((String)var3_2).append(" exec time is :").append(System.currentTimeMillis() - var1_1).append("ms").toString());


}


}</

相关文章
|
11月前
|
Arthas 监控 Java
开源Java诊断工具Arthas:开篇之watch实战
还在为排查Java程序线上问题头痛吗,看我们用阿里开源的诊断神器 Arthas 来帮您
350 1
|
7天前
|
Windows
技术经验分享:bootsect命令
技术经验分享:bootsect命令
|
2月前
|
存储 运维 Linux
精彩推荐 | 【深入浅出Docker原理及实战】「原理实战体系」零基础+全方位带你学习探索Docker容器开发实战指南(实战技术总结)
在使用Docker时,管理维护工作可能会显得复杂。然而,实际上,Docker提供了许多便捷且人性化的工具,这些工具的使用技巧可以大大简化维护工作,并提升效率。通过掌握这些技巧,你不仅能够更轻松地管理Docker环境,还能展现出专业的能力。接下来我们就给大家介绍一下对于我在工作当中对于Docker容器使用的技术实战总结
60 2
精彩推荐 | 【深入浅出Docker原理及实战】「原理实战体系」零基础+全方位带你学习探索Docker容器开发实战指南(实战技术总结)
|
Arthas 物联网 测试技术
《workshop专场--容器、消息&IoT专场-开发者动手实践营-容器、消息和IoT-Java诊断利器Arthas排查问题实践》电子版地址
workshop专场--容器、消息&IoT专场-开发者动手实践营-容器、消息和IoT-Java诊断利器Arthas排查问题实践
87 0
《workshop专场--容器、消息&IoT专场-开发者动手实践营-容器、消息和IoT-Java诊断利器Arthas排查问题实践》电子版地址
|
存储 运维 Kubernetes
docker 原理及在运维工作的地位和作用 | 学习笔记
快速学习docker 原理及在运维工作的地位和作用
166 0
docker 原理及在运维工作的地位和作用 | 学习笔记
|
存储 Kubernetes 监控
大规模运行 Apache Airflow 的经验和教训
Sam Wheating,来自加拿大不列颠哥伦比亚省温哥华的高级开发人员。供职于 Shopify 的数据基础设施和引擎基础团队。他是开源软件的内部倡导者,也是 Apache Airflow 项目的贡献者。
1088 0
大规模运行 Apache Airflow 的经验和教训
|
监控 Java 中间件
Pinpoint实践
关于pinpoint的安装及使用网上有很多教程就不介绍了。这里主要记录下碰到的问题
Pinpoint实践
|
jenkins 关系型数据库 MySQL
测试必会 Docker 实战(一):掌握高频命令,夯实内功基础
在 Dokcer 横空出世之前,应用打包一直是大部分研发团队的痛点。在工作中,面对多种服务,多个服务器,以及多种环境,如果还继续用传统的方式打包部署,会浪费大量时间精力。 在 Docker 出现后,它以更高效的利用系统资源、更高效的利用系统资源、一致的运行环境、持续交付和部署、更轻松的迁移、更轻松的维护和拓展,6 大优点迅速火了起来。 Docker 的基础命令,堪称 Docker 的内功,
|
安全 Java jvm-sandbox
JVM-Sandbox核心技术实现和架构,你想知道的都在这里了
听众收益: 1、学会解决问题的方法,体会思路决定出路的过程 2、了解JVM-Sandbox的核心技术实现和架构 3、了解JVM-Sandbox的使用场景以及产品化体系
3755 0
JVM-Sandbox核心技术实现和架构,你想知道的都在这里了

热门文章

最新文章