Java成神之路——javaAgent(插桩,attach)
javaAgent
Javaagent 是什么?
javaAgent运行类加载器在加载类之前对类做出动态的修改.
运行java命令执行时添加参数 -javaagent指定打包好的agent的jar即可以. 可以定义多个agent,按指定顺序执行
java -javaagent:agent1.jar -javaagent:agent2.jar -jar MyProgram.jar
字节码插桩,bTrace,Arthas 都是通过这种方式来实现。
javaAgent类方法
创建一个类里面定义agent方法
public static void premain(String agentArgs, Instrumentation inst)public static void premain(String agentArgs)
JVM 会优先加载 带 Instrumentation 签名的方法,加载成功忽略第二种,如果第一种没有,则加载第二种方法。Instrumentation是一个重要的参数。
Instrumentation 接口定义的方法
public interface Instrumentation {//增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。void addTransformer(ClassFileTransformer transformer, boolean canRetransform);//在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。void addTransformer(ClassFileTransformer transformer);//删除一个类转换器boolean removeTransformer(ClassFileTransformer transformer);boolean isRetransformClassesSupported();//在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;boolean isRedefineClassesSupported();void redefineClasses(ClassDefinition... definitions)throws ClassNotFoundException, UnmodifiableClassException;boolean isModifiableClass(Class<?> theClass);@SuppressWarnings("rawtypes")Class[] getAllLoadedClasses();@SuppressWarnings("rawtypes")Class[] getInitiatedClasses(ClassLoader loader);//获取一个对象的大小long getObjectSize(Object objectToSize);void appendToBootstrapClassLoaderSearch(JarFile jarfile);void appendToSystemClassLoaderSearch(JarFile jarfile);boolean isNativeMethodPrefixSupported();void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);
}
Java Agent - Hello World
使用 javaagent 需要几个步骤:
- 定义一个 MANIFEST.MF 文件,必须包含 Premain-Class 选项,通常也会加入Can-Redefine-Classes 和 Can-Retransform-Classes 选项。
- 创建一个Premain-Class 指定的类,类中包含 premain 方法,方法逻辑由用户自己确定。
- 将 premain 的类和 MANIFEST.MF 文件打成 jar 包。
- 使用参数 -javaagent: jar包路径 启动要代理的方法。
在执行以上步骤后,JVM 会先执行 premain 方法,大部分类加载都会通过该方法,注意:是大部分,不是所有。当然,遗漏的主要是系统类,因为很多系统类先于 agent 执行,而用户类的加载肯定是会被拦截的。也就是说,这个方法是在 main 方法启动前拦截大部分类的加载活动,既然可以拦截类的加载,那么就可以去做重写类这样的操作,结合第三方的字节码编译工具,比如ASM,javassist,cglib等等来改写实现类。
创建javaAgent类
public class HelloAgent {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("agentArgs : " + agentArgs);inst.addTransformer(new DefineTransformer(), true);}static class DefineTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {System.out.println("premain load Class:" + className);return classfileBuffer;}}
}
创建MANIFEST.MF
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.tttiger.HelloAgent
注意最后一行需要有空行。在idea中添加article,提示不接受不用管,idea会自动给你创建MANIFEST.MF可能会冲突,让idea创建,创建好后再去修改配置。
agent挂载运行
新创建一个类或jar,在运行的时候添加命令 -javaagent:e:/xxx.jar 打包完成的javaagentjar包路径。
使用maven进行打包
添加maven插件指定javaagent类,maven自动完成manifest配置,不用自己再去配置推荐
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.1.0</version><configuration><archive><!--自动添加META-INF/MANIFEST.MF --><manifest><addClasspath>true</addClasspath></manifest><manifestEntries><Premain-Class>com.rickiyang.learn.PreMainTraceAgent</Premain-Class><Agent-Class>com.rickiyang.learn.PreMainTraceAgent</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration>
</plugin>
MANIFEST.MF参数说明
Premain-Class :包含 premain 方法的类(类的全路径名)main方法运行前代理
Agent-Class :包含 agentmain 方法的类(类的全路径名)另一种代理main开始后可以修改类结构
Boot-Class-Path :设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)说白就是agent依赖的类
Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为 false(可选)
mainAgent
在 Java SE 6 的 Instrumentation 当中,提供了一个新的代理操作方法:agentmain,可以在 main 函数开始运行之后再运行。
跟premain函数一样, 开发者可以编写一个含有agentmain函数的 Java 类:
//采用attach机制,被代理的目标程序VM有可能很早之前已经启动,当然其所有类已经被加载完成,
//这个时候需要借助Instrumentation#retransformClasses(Class<?>... classes)
//让对应的类可以重新转换,从而激活重新转换的类执行ClassFileTransformer列表中的回调
public static void agentmain (String agentArgs, Instrumentation inst)public static void agentmain (String agentArgs)
agentMain 主要用于对java程序的监控,调用java进程,将自己编写的agentMain 注入目标完成对程序的监控,修改。
创建agentmain
public class TestMainAgent {public static void agentmain(String agentArgs, Instrumentation instrumentation) {System.out.println("loadagent after main run.args=" + agentArgs);Class<?>[] classes = instrumentation.getAllLoadedClasses();for (Class<?> cls : classes){System.out.println(cls.getName());}System.out.println("agent run completely.");}static class DefineTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {System.out.println("premain load Class:" + className);return classfileBuffer;}}
}
添加maven插件打包
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.1.0</version><configuration><archive><!--自动添加META-INF/MANIFEST.MF --><manifest><addClasspath>true</addClasspath></manifest><manifestEntries><Agent-Class>com.tttiger.TestMainAgent</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin></plugins></build>
测试agentMain插桩到其他类
另外启用了一个jvm进程,找到需要attach的jvm进程,让它加载agentMain,那么agentMain就会被加载到对方jvm执行。arthas就是使用这种方式attach进jvm进程,开启一个socket然后进行目标jvm的监控。
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, InterruptedException {//获取当前系统中所有 运行中的 虚拟机System.out.println("running JVM start ");List<VirtualMachineDescriptor> list = VirtualMachine.list();for (VirtualMachineDescriptor vmd : list) {//如果虚拟机的名称为 xxx 则 该虚拟机为目标虚拟机,获取该虚拟机的 pid//然后加载 agent.jar 发送给该虚拟机System.out.println(vmd.displayName());if (vmd.displayName().endsWith("com.tttiger.TestJVM")) {System.out.println(vmd.id());VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());virtualMachine.loadAgent("e:/test-agentMain-1.0-SNAPSHOT.jar");virtualMachine.detach();System.out.println("attach");}}Thread.sleep(10000L);}
VirtualMachine 字面意义表示一个Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了获取系统信息(比如获取内存dump、线程dump,类信息统计(比如已加载的类以及实例个数等), loadAgent,Attach 和 Detach (Attach 动作的相反行为,从 JVM 上面解除一个代理)等方法,可以实现的功能可以说非常之强大 。该类允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上 。
代理类注入操作只是它众多功能中的一个,通过loadAgent方法向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。
VirtualMachineDescriptor 则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能
通过VirtualMachine类的attach(pid)方法,便可以attach到一个运行中的java进程上,之后便可以通过loadAgent(agentJarPath)来将agent的jar包注入到对应的进程,然后对应的进程会调用agentmain方法。
Instrumentation的局限性
大多数情况下,我们使用Instrumentation都是使用其字节码插桩的功能,或者笼统说就是类重定义(Class Redefine)的功能,但是有以下的局限性:
- premain和agentmain两种方式修改字节码的时机都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。
- 类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation#redefineClasses()方法,此方法有以下限制:
2.1 新类和老类的父类必须相同;
2.2 新类和老类实现的接口数也要相同,并且是相同的接口;
2.3 新类和老类访问符必须一致。 新类和老类字段数和字段名要一致;
2.4 新类和老类新增或删除的方法必须是private static/final修饰的;
2.5 可以修改方法体。
除了上面的方式,如果想要重新定义一个类,可以考虑基于类加载器隔离的方式:创建一个新的自定义类加载器去通过新的字节码去定义一个全新的类,不过也存在只能通过反射调用该全新类的局限性。
参考文章链接
咸鱼IT技术交流群:89248062,在这里有一群和你一样有爱、有追求、会生活的朋友! 大家在一起互相支持,共同陪伴,让自己每天都活在丰盛和喜乐中!同时还有庞大的小伙伴团体,在你遇到困扰时给予你及时的帮助,让你从自己的坑洞中快速爬出来,元气满满地重新投入到生活中
Java成神之路——javaAgent(插桩,attach)相关推荐
- Java成神之路技术整理
转载自 Java成神之路技术整理 以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期更新. Java 基础篇 Java 集合篇 Java 多线程篇 J ...
- Java成神之路[转]
阿里大牛珍藏架构资料,点击链接免费获取 针对本文,博主最近在写<成神之路系列文章> ,分章分节介绍所有知识点.欢迎关注. 主要版本 更新时间 备注 v1.0 2015-08-01 首次发布 ...
- Alibaba技术专家倾心五年打造 Java成神之路:基础篇
近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是Java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...
- Java成神之路——ASM,Javassist,cglib区别。
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java成神之路技术整理,本文长期更新!
原文地址:https://mp.weixin.qq.com/s/N507Cfb_mbkGvHtg_FIaVg(来源:java技术栈微信公众号) 以下是Java技术栈微信公众号发布的所有关于 Java ...
- 【学海无涯】Java成神之路
基础篇 面向对象 面向对象与面向过程 面向过程就是按照程序进行的顺序依次编写索要完成相应任务的方法,依次调用.面型对象注重对逻辑概念的封装,将若干变量和方法封装成类,各个对象互相调用.面向对象占用 ...
- 夜光:Java成神之路(四)擅长的语言
夜光序言: 女孩问:"你喜欢我吗?" "不喜欢"男孩不假思索的回答 "哦"女孩失落地低下头,眼泪快要掉下来 "傻瓜,我不喜欢你,我 ...
- Java成神之路——JVM垃圾回收概览
如何确认对象可以被回收 枚举根节点,来确认, 搜索对象的引用链. 当一个对象的引用不能到达根节点,那么就认为这个对象是垃圾. 根节点可以为: 虚拟机栈中引用的对象,方法区中类静态属性引用的遍历,方法区 ...
- Java成神之路——CGLIB使用
什么是cglib CGLIB(Code Generator Library)是一个强大的.高性能的代码生成库.其被广泛应用于AOP框架(Spring.dynaop)中,用以提供方法拦截操作.Hiber ...
最新文章
- 华为AI再进化,CANN 3.0释放「算力狂魔」
- qt编写activex_Qt中使用ActiveX(一)
- css 解析 开源库_干货 | python库大全,全面高效
- 计算机语言无限循环,求大神帮我看看为什么我的子程序无限循环无法使用F8停止...
- SSH2整合需要jar包解释
- UI设计灵感|想让用户中心更有特色?这些案例值得借鉴
- 智能录音笔完成全系列布局,科大讯飞还发了一款转写翻译智能耳机
- js+运行+php+文件,php中运行JS
- 简单计算器与面积结合计算器
- vue 如何做到图片预览
- wps2016向程序发送命令_老司机帮您向程序发送命令时出现错误
【操作步骤】
的设置办法...
- 小米笔记本android,全球唯一 独家定制:「安卓中国版」小米笔记本电脑Air图赏...
- 机器学习:局部加权线性回归(Locally Weighted Linear Regression)
- 【资料分享】工程师必备物联网资料合集 电子书PDF
- Intel芯片组大全最新版
- 配色高手!一组有范又高级的深色网页设计案例解析
- 【5G之道】第六章:下行链路物理层处理
- Glide 问题 You cannot start a load for a destroyed activity
- 【java反射】简单说说静态代理和动态代理
- 怎么判断是程序存在问题还是软硬件系统存在问题