一个简单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;实际测试时候也会偶尔发现某些业务…

linux查看内核版本信息

使用命令 uname -acat /proc/version

C语言学习:snprintf()函数

函数原型&#xff1a; int snprintf(char* dest_str,size_t size,const char* format,...);函数功能&#xff1a; 先将可变参数 “...” 按照format的格式格式化为字符串&#xff0c;然后再将其拷贝至dest_str中。 头文件&#xff1a; #include<stdio.h>注意事项&…

物理 质点运动学

常用公式 重点 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)……

使用命令行的方式,将ini配置文件中的配置信息传递给程序

ini配置文件 {"device_type": "fake","device_socket": "192.168.1.108:5000"} 使用rpc的方式 ./bin/hsm_device_apitest --gtest_filter"*aes_test" --device-type rpc --device-socket 192.168.1.108:5000 使用fake的方…

C语言学习:malloc()函数

函数声明&#xff1a; void *malloc(size_t size)头文件&#xff1a; #include <stdio.h>函数描述&#xff1a; 分配所需的内存空间&#xff0c;并返回一个指向它的指针。 参数&#xff1a; size – 内存块的大小&#xff0c;以字节为单位。 返回值&#xff1a; 该…

java 希尔排序

希尔排序(更高效的插入排序) 减少最小数在最后一位的情况下要循环的次数 思路: 把数组按增量(n/2)分组,对每一组使用插入排序去排序交换位置,然后不停地增量/2,直到其为1时,结束 分组:如n/25 891723 8与3为一组 从不包含本身的数开始数两种实现方法: 交换法(效率较低) 移动法…

使用gtest进行自己的单独测试的代码介绍

命令行 ./bin/hsm_device_apitest --gtest_filter"*aes_test" --device-type rpc --device-socket 192.168.1.108:5000 命令详解 进入工程文件&#xff0c;mkdir build&#xff0c;cd build在build的文件夹下面执行cmake命令和make命令之后&#xff0c;会在build文…

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…

C++中vector章节iterator与const_iterator及const iterator区别

C目前倾向于使用迭代器遍历容器中的元素&#xff0c;而不是使用下标访问的方式来访问容器中的元素。可以使用iterator和const_iterator来访问元素&#xff0c;但是const类型的容器&#xff0c;那么只能用const_iterator来遍历。区别在于iterator可以改变元素的数值&#xff0c;…

Android查看当前应用已经加载的so库

源代码&#xff1a; private static List<String> allSOLists new ArrayList<String>();/** * 获取全部已加载的SO库*/private void getAllSOLoaded(){allSOLists.clear();// 当前应用的进程IDint pid Process.myPid();String path "/proc/" pid &q…

Android 进程监控(top命令)

文章目录一、查看top命令Android N&#xff08;7.1系统&#xff0c;level 25&#xff09; 及之前Android O&#xff08;8.0系统&#xff0c;level 26&#xff09; 及之后二、top -n [number]Android N&#xff08;7.1系统&#xff0c;level 25&#xff09; 及之前Android O&…

java 快速排序

快速排序 对冒泡排序的一种改进 思路: 一趟排序后,选取一个中间值,数组被分为比中间值小的部分,比中间值大的部分;再对左右两部分分别递归排序 代码实现 import java.util.Arrays;public class QuickSort {public static void main(String[] args) {int[] arr {-9, 78, 0, 2…