一个简单JavaAgent的实现

一、什么是javaagent

javaagent是一个JVM“插件”,一种专门精心制作的.jar文件,它能够利用JVM提供的Instrumentation API。

1.1、概要

Java Agent由三部分组成:代理类、代理类元信息和JVM加载.jar和代理的机制,整体内容如下图所示:
在这里插入图片描述

1.2、javaagent的基石

java.lang.instrument 为javaagent 通过修改方法字节码的方式操作运行在JVM上的程序提供服务。javaagent以JAR包的形式部署,JAR文件清单中的属性指定要加载的代理类,以启动代理。

javaagent的启动方式有以下几种:

  • 通过在命令行指定参数启动。

  • JVM启动后启动。例如,提供一种工具,该工具可以依附到已运行的应用,并允许在已运行的应用内加载代理。

  • 与应用一起打包为可执行文件。

1.3、启动 javaagent

1.3.1、命令行启动

命令行启动参数如下:

-javaagent:<jarpath>[=<options>]

<jarpath> :javaagent的路径,比如 /opt/var/Agent-1.0.0.jar
<options> : javaagent参数,参数的解析由javaagent负责。
javaagent JAR文件清单必须包含 Premain-Class 属性,属性的值为agent class的全路径名(包名+类名)。代理类必须实现 premain 方法,premain 方法和 main 方法一样分别是代理和应用的入口点。JVM初始化完成后首先调用代理的premain函数,然后调用应用的main函数,premain方法必须返回后进程才能启动。

premain 方法签名如下:

public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)

JVM首先尝试在代理中调用签名为1的方法,如果代理类没有实现签名为1的方法,JVM尝试调用签名为2的方法:

代理类可以有一个 agentmain函数,函数会在JVM启动完成之后调用。如果,使用命令行启动代理,agentmain 方式不会被调用。

代理的所有参数被当作一个字符串通过 agentArgs 变量传递,代理负责解析参数字符串。

如果代理因为代理类无法被加载、代理类未实现 premain 方法或抛出了未被捕获的异常,JVM将会退出。

javaagent的启动不要求实现一定提供命令行的方式,如果,实现支持通过命令行启动,实现必须支持在命令行中通过指定 -javaagent 参数启动。 -javaagent 可以在命令行中使用多次,启动多个代理。premain 函数的调用顺序和命令行中指定的顺序一致,多个代理可以使用相同 <jarpath>

没有一个严格模型来定义 premain 函数的工作范围,任何 main 函数可以做的工作,比如创建线程,在 premain 函数中都是合法的。

1.3.2、JVM启动后启动

实现可以提供在JVM启动之后再启动代理的机制。代理如何启动的细节特定于实现,通常应用程序已经启动,并且它的 main 方法已经被调用。如果实现支持在JVM启动后启动代理,代理必须满足以下条件:

  • 清单文件包含 Agent-Class 属性,属性的值为代理类全名。

  • 代理类必须实现 public static agentmain 方法。

agentmain方法有以下两个函数签名:

public static void agentmain(String agentArgs, Instrumentation inst)
public static void agentmain(String agentArgs)

JVM首先尝试调用具有签名1的方法,如果,代理类没有实现该方法,JVM尝试调用签名为2的方法。

代理类可以同时实现 premainagentmain 两个方法,当代理以命令行方式启动时,JVM调用 premain 函数,当代理在JVM启动之后启动时,JVM调用 agentmain 函数,而且JVM不会调用 premain 函数。

agentmain 函数参数的传递也是通过 agentArgs,所有参数组合为一个字符串,参数的解析由代理负责。

agentmain 函数必须完成启动代理所有必须的初始化动作,当启动完成后,agentmain 函数必须返回。如果,代理不能启动或抛出未捕获的异常,JVM都会退出。

1.3.3、打包为可执行文件

如果代理打包到可执行JAR文件中,可执行JAR文件的清单中必须包含 Launcher-Agent-Class 属性,指定一个在应用main函数调用之前代理启动的类。JVM尝试在代理上调用以下方法:

public static void agentmain(String agentArgs, Instrumentation inst)

如果,代理类没有实现上述方法,JVM则调用下面的方法。

public static void agentmain(String agentArgs)

agentArgs 参数的值必须为空字符串。

agentmain 函数必须完成代理启动必须的所有初始化动作并在启动后返回。如果,代理无法启动或抛出未捕获的异常,JVM会退出。

1.3.4、加载代理类以及代理类可用的模块/类

系统类加载器负责加载代理JAR文件中的所有类,并且成为系统类加载器的未命名模块的成员。 系统类加载器通常也定义包含应用程序 main 方法的类。对代理类可见的所有类都对系统类加载器可见,必须满足下面的最低要求:

  • 启动层中的模块导出的包中的类。 启动层是否包含所有平台模块取决于初始模块或应用程序的启动方式。

  • 类可被系统类加载器定义。

  • 启动类加载器定义的所有代理的类为其未命名模块的成员。

如果代理类需要链接到不在启动层中的平台(或其他)模块中的类,则需要以确保这些模块位于启动层中的方式启动应用程序。 例如,在JDK实现中,--add-modules 命令行选项可用于将模块添加到要在启动时解析的根模块集中。

启动类加载器可以加载代理支持的类(通过 appendToBootstrapClassLoaderSearch 或指定Boot-Class-Path属性)必须仅链接到定义启动类加载器的类。 无法保证启动类加载器可以在所有平台工作。

如果配置了自定义系统类加载器(通过 getSystemClassLoader 方法中指定的系统属性 java.system.class.loader ),则必须定义 appendToSystemClassLoaderSearch 中指定的 appendToClassPathForInstrumentation 方法。 换句话说,自定义系统类加载器必须支持将代理JAR文件添加到系统类加载器搜索范围内的机制。

1.4、javaagent清单属性

属性说明是否必选默认值
Premain-Class包含premain方法的类依赖启动方式
Agent-Class包含agentmain方法的类依赖启动方式
Boot-Class-Path启动类加载器搜索路径
Can-Redefine-Classis是否可以重定义代理所需的类false
Can-Retransform-Classis是否能够重新转换此代理所需的类false
Can-Set-Native-Method-Prefix是否能够设置此代理所需的本机方法前缀false

二、写一个Java Agent

基于上面的介绍,我们实现一个下载JVM中所有非系统类的javaagent。

整个开发过程包括以下三步:

  • 1)定义代理类,实现类下载功能;

  • 2)配置、打包;

  • 3)命令行启动测试。

2.1、代理类实现

实现 premain 函数

package io.ct.java.agent;import java.lang.instrument.Instrumentation;public class AgentApplication {public static void premain(String arg, Instrumentation instrumentation) {System.err.println("agent startup , args is " + arg);// 注册我们的文件下载函数instrumentation.addTransformer(new DumpClassesService());}
}

文件下载类实现 ClassFileTransformer 接口,在类被加载时下载类的字节码:

package io.ct.java.agent;import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.List;/*** Copyright (C), 2018-2018, open source* FileName: DumpClassesService** @author : 大哥* Date:     2018/12/8 21:01*/
public class DumpClassesService implements ClassFileTransformer {private static final List<String> SYSTEM_CLASS_PREFIX = Arrays.asList("java", "sum", "jdk");@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if (!isSystemClass(className)) {System.out.println("load class " + className);FileOutputStream fos = null;try {// 将类名统一命名为classNamedump.class格式fos = new FileOutputStream(className + "dump.class");fos.write(classfileBuffer);fos.flush();} catch (IOException ioe) {ioe.printStackTrace();} finally {// 关闭文件输出流if (null != fos) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}return classfileBuffer;}/*** 判断一个类是否为系统类** @param className 类名* @return System Class then return true,else return false*/private boolean isSystemClass(String className) {// 假设系统类的类名不为NULL而且不为空if (null == className || className.isEmpty()) {return false;}for (String prefix : SYSTEM_CLASS_PREFIX) {if (className.startsWith(prefix)) {return true;}}return false;}
}

2.2、配置MANIFEST.MF

MANIFEST.MF 文件两种方式生成:手动配置和自动生成,手动配置只需要在 resources 文件下创建 META-INF/MENIFEST.MF 文件即可。除去手动配置外,可以使用maven插件在打包阶段自动生成,maven的插件配置如下:

             <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><manifestEntries><Premain-Class>io.ct.java.agent.AgentApplication</Premain-Class><Agent-Class>io.ct.java.agent.AgentApplication</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin>

生成的jar包格式如下:
在这里插入图片描述
其中MANIFEST.MF的文件内容如下(不同的配置生成的文件内容不完全一致):

Manifest-Version: 1.0
Implementation-Title: agent
Premain-Class: io.ct.java.agent.AgentApplication
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: chentong
Agent-Class: io.ct.java.agent.AgentApplication
Can-Redefine-Classes: true
Implementation-Vendor-Id: io.ct.java
Can-Retransform-Classes: true
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_171
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-boot-starter-parent/agent

2.3、命令行启动Java Agent

执行下面的命令,运行已经编译好的类Hello,可以在同级目录下生成一个名为Hellodump.class的文件。

java -javaagent:agent-0.0.1-SNAPSHOT.jar Hello

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Openai的openai新版本调用方式

最近大家有没有发现Openai的openai已经更新到1.6.1了,而且API的调用方式发生了巨大的变化,下面来看看openai新的调用方式吧。 欢迎关注公众号 module ‘openai’ has no attribute ChatCompletion. 提示openai的版本过低。(pip install -U openai) 1. Chat API from openai…

计算机系统基础 计算机系统的基本组成与基本功能

基础知识点 1.1946年第一台通用电子计算机ENIAC诞生 2.冯.诺依曼结构: 组成:输入设备,输出设备,存储器,运算器,控制器 3.现代计算机结构模型: 组成 CPU中央处理器 PC程序计数器 IR指令寄存器 ALU算数逻辑部件 GPRs通用寄存器组 MAR存储器地址寄存器 MDR存储器数据寄存器 知…

Android UI卡顿监控

一、背景 应用的使用流畅度&#xff0c;是衡量用户体验的重要标准之一。Android 由于机型配置和系统的不同&#xff0c;项目复杂App场景丰富&#xff0c;代码多人参与迭代历史较久&#xff0c;代码可能会存在很多UI线程耗时的操作&#xff0c;实际测试时候也会偶尔发现某些业务…

物理 质点运动学

常用公式 重点 1.求轨道方程:消去时间t 2.dr---->位置矢量大小的增量 3.求方向:tanax/y 4.求位置也就是求位移rxiyj即可 习题解析 1.求运动时一定要求出加速度,变速与匀速就是看a 2.求位移时必须看X0是不是为0,如果不为0,求位移与路程时都要减去x0 3. 记住等号两边统一…

使用CLion的时候,对于cmake的使用

问题概述 使用CLion的时候&#xff0c;一个大的项目会有一个总的CMakeLists.txt&#xff0c;这个是控制整个项目的编译环境&#xff0c;但是针对测试的代码会有自己的单独的CMakeLists.txt&#xff0c;这个单独的cmake文件是控制自己的程序所需要的环境即使是编译单独的测试程…

Android NDK之静态/动态注册Native方法

一、简介 关于NDK有两种方法注册&#xff1a;静态注册和动态注册。 静态注册&#xff1a; 就是直接在Java文件里写个native方法 然后再c/c文件中实现这个方法就行了&#xff1b;动态注册&#xff1a; 就是为了不要写很长的方法名&#xff0c;用JNI_OnLoad方法实现预注册&…

概率论 条件概率 全概率 贝叶斯公式

常用知识点 条件概率 1.P(B|A)1表示A发生的情况下B必然发生 A属于B 2.可列可加性 P(BUC|A)P(B|A)P(C|A) 3.P(B|A)的样本空间为A,A与B都发生了 大题解答思路 1.首先设取出一件商品为次品为事件A 2.写B1:甲生产,B2:乙生产 PB1…PB2… P(A|B1)…P(A|B2)… 3.写PAPB1*P(A|B1)……

C语言学习:%d、2d、02d、.2d的区别

%d&#xff1a;为普通的输出。 %2d&#xff1a;按宽度为2输出&#xff0c;右对齐方式输出。若不够两位&#xff0c;左边补空格。 %02d&#xff1a;同样宽度为2&#xff0c;右对齐方式。位数不够&#xff0c;左边补0。 %.2d&#xff1a;从执行效果来看&#xff0c;与%02d一样…

计算机系统基础 数据的表示和存储

数制和编码 1.信息的二进制编码 2.进制转换必须要知道: 1)使用哪一个进制(二,八…) 2)定点数还是浮点数(关于小数点的问题) 3)编码问题----原码,补码,反码,移码 3.进制转换 1)R进制转十进制(按权展开) ----R进制 ----八进制与十六进制 ----R转换为十进制 2)十进制转换为R…

物理 常见力与牛顿三定律

常用知识点 动量 dmvdmvdvm p-mv- f-dp-/dtma- 开普勒第三定律 r1^3__k只与恒星质量有关 T^2 总结 1.电梯匀速就相当于在地面,加速或减速就会有一个a 2.当合外力为0时,物体保持静止或匀速直线运动 3.力是改变物体运动状态的原因 4.重力在地球两极最大,赤道最小,随纬度…

Java命令:jmap — 打印指定进程的共享对象内存映射或堆内存细节

文章目录一、前言二、命令介绍三、使用实例1、jmap -heap [pid]2、jmap -histo[:live] [pid]3、jmap -histo[:live] [pid] |grep "[关键字1]\|[关键字2]"4、jmap -dump:live,formatb,filea.log [pid]四、总结一、前言 jdk安装后会自带一些小工具&#xff0c;jmap命令…

概率论 一维随机变量

随机变量 离散型随机变量:有限个或无限可列个 连续型随机变量 分布函数F(X) 范围是[a,b) 包含能取到a以及a之前的值的概率相加 分布律(概率分布) 1.所有概率相加为1 2.WX-1,计算出每一个对应的W,然后如果有相同的W就合并其概率,最后一一对应P(x)即可 概率密度函数(密度) …

JAVA牛客专项练习2020.12.31

1.使用迭代器的remove方法&#xff0c;可以边遍历边删除元素 2.线程 启动线程 new thread&#xff08;&#xff09;.start&#xff08;&#xff09; new thread&#xff08;new runnable&#xff08;&#xff09;&#xff09;.start&#xff08;&#xff09; 普通方法&#xf…

安卓牛客专项练习2020.12.31

1.窗口dialog或半透明 2.Pracelable性能比serializable高

安卓系统体系架构

1.大体:共有四层&#xff0c;系统应用层&#xff0c;JAVA API层&#xff0c;安卓系统运行层&#xff0c;Linux内核层 具体: 系统应用层&#xff08;System Apps&#xff09; Java API 框架层&#xff08;Java API Framework&#xff09; Android系统运行层&#xff08;包括Andr…

Java命令:jstack — 获取线程dump信息

目录一、命令介绍二、使用实例实例一&#xff1a;jstack查看输出实例二&#xff1a;jstack统计线程数实例三&#xff1a;jstack检测死锁实例四&#xff1a;jstack检测CPU高一、命令介绍 Usage:jstack [-l] <pid>(to connect to running process) //连接活动线程jstack …

Java多线程死锁例子

目录一、产生死锁的原因二、如何避免死锁一、产生死锁的原因 发生死锁的情况&#xff1a; 多个线程需要同时占用多个共享资源而发生需要互相死循环等待的情况&#xff0c;就是&#xff0c;两个线程互相等待着对象释放锁&#xff0c;一直这样僵持下去&#xff0c;所以导致了死锁…

安卓四大组件简介

安卓四大组件 Activity活动&#xff0c;Service服务&#xff0c;BroadcastRecevicer广播接受器&#xff0c;Content Provider内容提供者 Activity活动 所有程序的流程都运行在activity中 Service服务 只能后台运行&#xff0c;没有界面的长生命周期的代码 BroadcastRece…

WebLogic域的创建与发布

目录一、前言二、准备三、创建域步骤第一步&#xff1a;直接【回车】第二步&#xff1a;直接【回车】第三步&#xff1a;直接【回车】第四步&#xff1a;输入域名称后【回车】第五步&#xff1a;直接【回车】第六步&#xff1a;直接【回车】&#xff08;此步骤是提示域的存放目…