原文网址:Java探针--javaagent--使用/实例_IT利刃出鞘的博客-CSDN博客

简介

本文用示例介绍javaagent的用法。

javaagent简介

JavaAgent 是JDK 1.5 以后引入的,也叫做Java代理。

javaagent的作用

  • 可以在加载java文件之前进行拦截,修改字节码。
  • 可以在运行期间修改已经加载的类的字节码。
    • 这种用法有很多的限制。
  • javaagent结合javassist功能更强大:可以创建类、方法、变量等。

这实际上提供了一种虚拟机级别的 AOP 实现方式。通过以上方法就能实现对一些框架或是技术的采集点进行字节码修改,完成这些功能:对应用进行监控,对执行指定方法或是接口时额外添加操作(打印日志、打印方法执行时间、采集方法的入参和结果等)。

很多APM监控系统就是基于此实现的,例如:Arthas、SkyWalking。

javaagent的使用方式

  • 方式1:在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 -javaagent 参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。

    • -javaagent 这个参数的个数是不限的,如果指定了多个,则会按指定的先后执行,执行完各个 agent 后,才会执行主程序的 main 方法。例如:
      java -javaagent:D:\workspace\javaagent.jar=hello1 -javaagent:D:\workspace\javaagent.jar=hello2 -jar D:\workspace\myTest.jar
  • 方式2:在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 Java Tool API 中的 attach 方式指定进程id和特定jar包地址,启动 Instrumentation 的代理程序。

javaagent其他的功能

  • 获取所有已经被加载过的类
  • 获取所有已经被初始化过了的类(执行过了clinit方法,是上面的一个子集)
  • 获取某个对象的大小
  • 将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载
  • 将某个jar加入到classpath里供AppClassload去加载
  • 设置某些native方法的前缀,主要在查找native方法的时候做规则匹配

静态agent与动态agent

Agent分为如下两种:

  • 静态Instrument:在main加载之前运行的Agent
  • 动态Instrument:在main运行之后运行的Agent(JDK1.6以后提供)。

静态Instrument(启动时)加载Instrument过程

  1. 创建并初始化 JPLISAgent;
  2. 监听VMInit事件,在JVM初始化完成之后做下面的事情:
    1. 创建InstrumentationImpl对象;
    2. 监听ClassFileLoadHook事件;
    3. 调用InstrumentationImpl的loadClassAndCallPremain方法,在这个方法里会去调用javaagent中MANIFEST.MF里指定的Premain-Class类的premain方法 ;
  3. 解析javaagent中MANIFEST.MF文件的参数,并根据这些参数来设置JPLISAgent里的一些内容。

动态Instrument运行时加载Instrument过程

通过JVM的attach机制来请求目标JVM加载对应的agent,过程大致如下:

  1. 创建并初始化JPLISAgent;
  2. 解析 javaagent 里 MANIFEST.MF 里的参数;
  3. 创建 InstrumentationImpl 对象;
  4. 监听 ClassFileLoadHook 事件;
  5. 调用 InstrumentationImpl 的loadClassAndCallAgentmain方法,在这个方法里会去调用javaagent里 MANIFEST.MF 里指定的Agent-Class类的agentmain方法。

示例1:简单用法

agent程序

项目结构

1.提供premain方法

package com.example.a;import java.lang.instrument.Instrumentation;public class DemoAgent {/*** 该方法在main方法之前运行,与main方法运行在同一个JVM中*/public static void premain(String arg, Instrumentation instrumentation) {System.out.println("agent的premain(String arg, Instrumentation instrumentation)方法");}/*** 若不存在 premain(String agentArgs, Instrumentation inst),* 则会执行 premain(String agentArgs)*/public static void premain(String arg) {System.out.println("agent的premain(String arg)方法");}
}

2.提供META-INF/MANIFEST.MF

在src/main/java的同级目录下新建META-INF文件夹,在里边新建MANIFEST.MF文件(注意最后一行必须是空行)

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.example.a.DemoAgent
  • Premain-Class :包含 premain 方法的类(类的全路径名)
  • Agent-Class :包含 agentmain 方法的类(类的全路径名)
  • Boot-Class-Path :设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
  • Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false(可选)
  • Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
  • Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为 false(可选)

3.将其打包为jar包

步骤1:打包的配置入口

File=> Project Structure=> Project Settings=> Artifacts=> + => JAR=> From modules with dependencies...

步骤2:打包的配置

步骤3:打包

Build=> Build Artifacts...=> Build

此时会生成out目录,并生成jar包:

也可使用maven配置META-INF/MANIFEST.MF

使用maven,打包方便,而且不用手写META-INF/MANIFEST.MF,用插件即可:

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.5.1</version><!-- 指定maven编译的jdk版本。若不指定,maven3默认用jdk 1.5 maven2默认用jdk1.3 --><configuration><source>8</source><target>8</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.2.0</version><configuration><archive><!--自动添加META-INF/MANIFEST.MF --><manifest><addClasspath>true</addClasspath></manifest><manifestEntries><Menifest-Version>1.0</Menifest-Version><Premain-Class>com.example.a.DemoAgent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin></plugins>
</build>

maven的项目结构为:

应用程序

项目结构

1.提供main程序

package com.example.a;public class Demo {public static void main(String[] args) {System.out.println("应用的main方法");}
}

2.打包

打包方法和agent的项目略有不同。见:Intellij IDEA--将Java项目打包为jar包--方法/实例_IT利刃出鞘的博客-CSDN博客

测试

java -javaagent:D:\tmp\demo_javaagent.jar -jar demo_java.jar

结果:

示例2:统计方法的执行时间

需求:写一个agent,统计应用的某个方法的执行时间。(本处要统计的方法是:TimeTest#test方法)

agent程序

agent代码

package com.example.a;import java.lang.instrument.Instrumentation;public class DemoAgent {/*** 该方法在main方法之前运行,与main方法运行在同一个JVM中*/public static void premain(String arg, Instrumentation instrumentation) {System.out.println("agent的premain(String arg, Instrumentation instrumentation)方法");instrumentation.addTransformer(new MyTransformer());}/*** 若不存在 premain(String agentArgs, Instrumentation inst),* 则会执行 premain(String agentArgs)*/public static void premain(String arg) {System.out.println("agent的premain(String arg)方法");}
}

Transformer代码

package com.example.a;import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;public class MyTransformer implements ClassFileTransformer {private final String injectedClass = "com.example.a.TimeTest";private final String injectedMethod = "test";@Overridepublic byte[] transform(ClassLoader loader,String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {String realClassName = className.replace("/", ".");if (realClassName.equals(injectedClass)) {CtClass ctClass;try {// 使用全称,取得字节码类<使用javassist>ClassPool classPool = ClassPool.getDefault();ctClass = classPool.get(realClassName);// 得到方法实例CtMethod ctMethod = ctClass.getDeclaredMethod(injectedMethod);// 添加变量ctMethod.addLocalVariable("time", CtClass.longType);ctMethod.insertBefore("System.out.println(\"------------ Before --------\");");ctMethod.insertBefore("time = System.currentTimeMillis();");ctMethod.insertAfter("System.out.println(\"Elapsed Time(ms): \" + (System.currentTimeMillis() - time));");ctMethod.insertAfter("System.out.println(\"------------- After --------\");");return ctClass.toBytecode();} catch (Throwable e) { //这里要用Throwable,不要用ExceptionSystem.out.println(e.getMessage());e.printStackTrace();}}// 返回原类字节码return classfileBuffer;}
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>demo_javaagent</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.28.0-GA</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.5.1</version><!-- 指定maven编译的jdk版本。若不指定,maven3默认用jdk 1.5 maven2默认用jdk1.3 --><configuration><source>8</source><target>8</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.2.0</version><configuration><archive><!--自动添加META-INF/MANIFEST.MF --><manifest><addClasspath>true</addClasspath></manifest><manifestEntries><Menifest-Version>1.0</Menifest-Version><Premain-Class>com.example.a.DemoAgent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin></plugins></build></project>

应用程序

main类

package com.example.a;public class Demo {public static void main(String[] args) {System.out.println("应用的main方法");new TimeTest().test();}
}

测试类

package com.example.a;public class TimeTest {public void test() {System.out.println("开始执行TimeTest#test");System.out.println("sleep开始");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("sleep结束");}
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>demo_maven</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.28.0-GA</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.5.1</version><!-- 指定maven编译的jdk版本。若不指定,maven3默认用jdk 1.5 maven2默认用jdk1.3 --><configuration><source>8</source><target>8</target></configuration></plugin><plugin><artifactId>maven-assembly-plugin</artifactId><configuration><archive><manifest><mainClass>com.example.a.Demo</mainClass></manifest></archive><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><appendAssemblyId>false</appendAssemblyId></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>assembly</goal></goals></execution></executions></plugin></plugins></build>
</project>

测试

java -javaagent:D:\tmp\demo_javaagent-1.0-SNAPSHOT.jar -jar demo_maven-1.0-SNAPSHOT.jar

结果:

Java探针--javaagent--使用/实例相关推荐

  1. Java探针之JavaAgent揭秘

    java探针利用了javaAgent + ASM字节码注入工具实现了动态修改类文件的功能.这是很多arthas和APM应用监控系统如Skywalking的基础. 首先什么是JavaAgent呢? 什么 ...

  2. java探针之修改类字节码文件

    java探针利用了javaAgent + ASM字节码注入工具实现了动态修改类文件的功能.像skywalking和arthas都使用到了这个技术. 具体原理为: jdk1.5以后引入了javaAgen ...

  3. 【JAVA基础☞探针技术】Java探针-Java Agent技术

    个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈 1.原理:基于javaAgent和Java字节码注入技术的java探针工具技术原理 2.原理分析 动态代理功能实现说明,我们利用ja ...

  4. JAVA探针机制—Agent(一)

    JAVA探针机制-Agent(一) agent机制首次出现在JDK5版本,在JDK6版本得到升级并且正式被官方定义为agent原理. 首先要明确JavaAgent是一个JVM层面的插件,他可以利用JD ...

  5. 听云-java探针安装使用

    1.进入听云server,创建应用 进去点击新建应用 2.下载java探针 3.解压tingyun-agent-java.zip 拷贝tingyun目录到应用服务器的根目录 修改tingyun.pro ...

  6. 总结:Java探针技术

    一.介绍 探针技术通过JVM启动参数的 标准参数的-javaagent实现,原理是 在JVM加载class二进制文件的时候,动态的修改加载的class文件,在监控的方法前后添加计时器功能,用于计算监控 ...

  7. java中删除sqlite数据库语句_sqlite数据库的介绍与java操作sqlite的实例讲解

    sqlite数据库的介绍与java操作sqlite的实例讲解 发布时间:2020-10-03 05:40:34 来源:脚本之家 阅读:92 作者:Lee_Tech sqlite是啥? 1.一种轻型数据 ...

  8. java 字节码增强原理_深入浅出Java探针技术1--基于java agent的字节码增强案例

    Java agent又叫做Java 探针,本文将从以下四个问题出发来深入浅出了解下Java agent 一.什么是java agent? Java agent是在JDK1.5引入的,是一种可以动态修改 ...

  9. tcp网络通信教程 java_基于java TCP网络通信的实例详解

    JAVA中设计网络编程模式的主要有TCP和UDP两种,TCP是属于即时通信,UDP是通过数据包来进行通信,UDP当中就会牵扯到数据的解析和传送.在安全性能方面,TCP要略胜一筹,通信过程中不容易出现数 ...

最新文章

  1. 3D点云点云分割、目标检测、分类
  2. Shell编程进阶篇(完结)
  3. 第三篇 :Mysql存储引擎、数据导入导出、管理表记录、匹配条件
  4. matlab算线性方程解,MATLAB计算方法3解线性方程组计算解法.pptx
  5. 电池的寿命(信息学奥赛一本通-T1229)
  6. 面对安利,谁能笑到最后
  7. JVM学习-直接内存
  8. 广义多目标算法探索实践
  9. gVim 64位 Windows 7 安装过程 (使用spf13配置)
  10. 【note】编程范式(编程范型)的含义和种类,多范式编程语言
  11. r 语言ylim = c(0 1),今日R--条形图(barplot)
  12. xml中处理特殊字符和转义字符
  13. linux centos设置共享目录,在CentOS上配置SAMBA共享目录
  14. 2月20日 梯度下降、三种方式、三种超参数学习率冲量衰减因子、应用举例
  15. LabVIEW编程LabVIEW开发 固高运动控制器例程与相关资料
  16. CCS 报警告 #10247-D
  17. 鬼谷八荒逆天改命词条通过C++代码制作
  18. android app 瘦身,android 将程序移入system/app 为系统瘦身
  19. 基于CUDA的并行lammps编译及测试
  20. 车机软件测试ADBShell命令集合

热门文章

  1. java计算机毕业设计固定资产管理系统MyBatis+系统+LW文档+源码+调试部署
  2. 2月19号吃鸡服务器维护多久,绝地求生8月19日更新维护公告
  3. microtime 获取当前 Unix 时间戳和微秒数
  4. android70怎么升级,梦幻西游手游70级后快速升级方法
  5. MySQL应用之CROSS JOIN用法(有时挺管用)
  6. 2022-06-23 轮播库swiper.js的引入和使用
  7. mysql基于Java的学生请销假审批管理系统的设计与实现毕业设计源码130939
  8. Apache couchdb 命令执行 (CVE-2017-12636)复现
  9. 【bzoj 3309】 DZY Loves Math
  10. 获取电脑当前正在连接的wifi密码