原文网址: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. 避免让网站沦为摆设,从三个方面着手塑造!
  2. java学习之单例模式(饿汉式与懒汉式)
  3. 大型运输行业实战_day12_1_权限管理实现
  4. 如何让nginx执行python代码_生产环境部署Python语言代码(django+uwsgi+nginx)
  5. linux mint 19界面美化,安装完 LinuxMint 19.3 后必做的10件事
  6. 上市即巅峰!走乐视老路的暴风 实控人冯鑫是下一个贾跃亭?
  7. java校验参数防止攻击_程序员写接口参数校验,总是太多if else?一招让你避免体力活...
  8. node.js删除文件
  9. 【书评:Oracle查询优化改写】第三章
  10. html2canvas 在手机app端的问题
  11. Django一些常用操作记录
  12. vue-cropper 截图
  13. Linux系统在Xshell6布置定时任务
  14. 高德地图获取坐标距离_高德地图计算两坐标之间距离
  15. 命令启动oracle实例,【单选题】启动oracle数据库实例的命令是
  16. 机器学习的L1、L2损失函数
  17. 基于PP-ShiTu的商品识别系统
  18. 关于客户端断开连接后服务器抛出异常Connection reset
  19. 互联网金融的前世、今生和未来-系列四(今生):百花齐放的互联网金融业态
  20. JavaEE——HTTP协议

热门文章

  1. 关于java字符串拼接处理方法的总结
  2. Git学习笔记——3、文件的新建、暂存、提交及重新提交
  3. android浮浮窗广告实现,浮窗广告的实现原理
  4. python缩进的用途和使用方法_关于Python缩进,我们该了解哪些?
  5. 计算机新建表格2,怎么建表格-只需2步,创建Excel“超级表”,让表格变得更强大...
  6. 【Unity 学习笔记】 Sprite的部分属性和功能
  7. React Native 红屏报错 Unable to load script from assets
  8. 小米不花一分钱把用户做到10000万的秘密
  9. 人工智能风口,Python程序员的狂欢与企业主的哀嚎。
  10. 休假时自动回复邮件怎么写?