技术经验分享: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());


}


}</

相关文章
|
8月前
|
监控 Oracle Java
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,探索各大JVM虚拟机特色 —— JVM故障排除指南(先导篇)
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,探索各大JVM虚拟机特色 —— JVM故障排除指南(先导篇)
134 0
|
8月前
|
Oracle Java 编译器
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
105 1
|
4月前
|
Java 编译器 Maven
【颠覆你的认知!】当Quarkus邂逅GraalVM本机镜像,应用启动竟快到飞起——背后的技术秘密等你揭秘!
Quarkus框架因轻量级与高性能而在Java开发社区广受关注。结合GraalVM使用能显著提升应用启动速度与运行效率,这得益于GraalVM的本机镜像支持。本文将介绍如何利用Quarkus和GraalVM构建高效应用,并提供示例代码演示具体步骤。首先需安装GraalVM环境并配置Maven支持构建本机镜像。接着创建一个简单的RESTful服务端点作为示例,通过命令行编译生成本机可执行文件并运行。这种方式能够大幅提升应用性能,但需注意构建时间和部分Java特性兼容性问题。
75 1
|
5月前
|
敏捷开发 设计模式 开发者
【揭秘终极利器】AgileEAS.NET:服务定位器模式的魔法,如何让企业级软件开发瞬间提速?揭秘背后的技术奥秘与实战指南!
【8月更文挑战第16天】AgileEAS.NET是基于DotNet的企业级敏捷开发平台,其服务定位器模式助力构建高度解耦系统。通过全局服务目录动态查找服务,避免硬编码依赖。在AgileEAS.NET中,服务定位器以静态类形式封装服务注册与检索功能。示例展示了如何注册与获取服务实例,如在`UserController`中通过服务定位器使用`IUserService`。此模式整合到框架生命周期管理,便于各处获取服务实例,提升开发效率。然而,应适度使用并考虑依赖注入容器以增强代码可维护性和可测试性。
90 4
|
7月前
|
Windows
技术经验分享:bootsect命令
技术经验分享:bootsect命令
10avalon - vm运作原理
10avalon - vm运作原理
59 0
|
前端开发 Java 物联网
GIAC-2022sh 学习笔记 | WebAssembly在前端中的应用与展望
GIAC-2022sh 学习笔记 | WebAssembly在前端中的应用与展望
379 0
GIAC-2022sh 学习笔记 | WebAssembly在前端中的应用与展望
|
安全 Java jvm-sandbox
JVM-Sandbox核心技术实现和架构,你想知道的都在这里了
听众收益: 1、学会解决问题的方法,体会思路决定出路的过程 2、了解JVM-Sandbox的核心技术实现和架构 3、了解JVM-Sandbox的使用场景以及产品化体系
3872 0
JVM-Sandbox核心技术实现和架构,你想知道的都在这里了
|
Java API Apache
走进JavaWeb技术世界9:Java日志系统的诞生与发展
本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下Star哈 文章首发于我的个人博客: www.how2playlife.com 本文是微信公众号【Java技术江湖】的《走进JavaWeb技术世界》其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,如有侵权,请联系作者。
|
jvm-sandbox Java 容器
JVM-SANDBOX:从阿里精准测试走出的开源贡献奖
稳定性是历年双11的技术质量保障核心。从 2016 年开始淘宝技术质量部潜心修行,创新地研发了一套实时无侵入的字节码增强框架,于是「JVM-SANDBOX」诞生了,并且顺手在 MTSC 大会上拿了开源贡献奖,今天,我们来瞅瞅这个拿奖的项目。
2871 0