Java Agent -- Instrumentation

1 Instrumentation简介

使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。

Instrumentation是从Java 5后存在的重要特性,Java 6又对其多了一系列优化。详见 http://www.ibm.com/developerworks/cn/java/j-lo-jse6/

在 Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能。

在 Java SE 5 中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作。但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了 instrument 的应用。

Java SE 6 的新特性改变了这种情况,通过 Java Tool API 中的 attach 方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。

在实际中,这两种方式现在都被广泛使用甚至同时存在于一个项目中。下面我们就来具体看看这两种方式的具体做法。

2 Instrumentation的基本用法

2.1 Premain方式

Premain即为Java 5中的在运行前设置代理类的方式。

我们可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 – javaagent参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。

简单步骤

步骤一:创建Premain类,编写premain函数

编写一个 Java 类,包含如下两个方法当中的任何一个

代码块

Plain Text

public static void premain(String agentArgs, Instrumentation inst); //优先级更高,若两者都存在,只会执行该方法
public static void premain(String agentArgs);
  • agentArgs 是 premain 函数得到的程序参数,随同 “– javaagent”一起传入。与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。

  • Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等

在这个 premain 函数中,开发者可以进行对类的各种操作。

步骤二:添加manifest

在src/main/resources/META-INF中创建MANIFEST.MF文件,属性当中加入” Premain-Class”来指定步骤 1 当中编写的那个带有 premain 的 Java 类。

代码块

Plain Text

Manifest-Version: 1.0
Premain-Class: Premain
步骤三:打jar包

如果是用maven管理的工程,打包前可以加入maven-jar-plugin插件,以便用-jar来执行。并且可以控制相应的manifest文件和main函数

代码块

Plain Text

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifestFile>
src/main/resources/META-INF/MANIFEST.MF
</manifestFile>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>UserClassMain</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
步骤四:启动

代码块

Plain Text

java -javaagent:java-agent-demo-1.0-SNAPSHOT.jar -jar java-agent-demo-1.0-SNAPSHOT.jar

如果要传入参数agentArgs,则如下:

代码块

Plain Text

java -javaagent:java-agent-demo-1.0-SNAPSHOT.jar=hello,world -jar java-agent-demo-1.0-SNAPSHOT.jar

注意,路径后直接跟=号,后面参数不能有空格。

代码示例

代码块

Plain Text

/*用户的类*/
public class UserClass {
public int getNum() {
return 1;
}
}
 
/*用户的main函数所在类*/
public class UserClassMain {
public static void main(String[] args) {
System.out.println("Execute User Class Main Method! result="+new UserClass().getNum());
}
}
 
/*Premain类*/
public class Premain {
public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println("agentArgs=" + agentArgs);
}
}
执行结果

19361061

2.2 Agentmain方式

Premain只能在main函数执行前进行处理,Agentmain则可以在程序启动后再运行。这就使得我们给程序增加监控的时候可以不用停止应用。

Agentmain和Premain的方式较相似,区别有三处:

1、Agentmain类

需要用Agentmain类替代之前的Premain类,Agentmain类也必须还有一个agentmain的函数:

代码块

Plain Text

public static void agentmain(String agentArgs, Instrumentation inst); //优先级更高,若两者都存在,只会执行该方法
public static void agentmain(String agentArgs);

2、添加manifest

类似的,添加manifest文件,属性当中加入” Agent-Class””来指定步骤 1 当中编写的那个带有agentmain的Java类。 

代码块

Plain Text

Manifest-Version: 1.0
Agent-Class: Agentmain

3、启动

要启动agentmain,就需要引入一个新的包com.sun.tools.attach。Attach API 不是 Java 的标准 API,而是 Sun 公司提供的一套扩展 API,用来向目标 JVM ”附着”(Attach)代理工具程序的。有了它,开发者可以方便的监控一个 JVM,运行一个外加的代理程序。

Attach API 很简单,只有 2 个主要的类:

  • VirtualMachine 代表一个 Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举,Attach 动作和 Detach 动作(Attach 动作的相反行为,从 JVM 上面解除一个代理)等等 ;

  • VirtualMachineDescriptor 则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能。

以下的例子表示在启动当前jvm中插入一个agent。

代码块

Plain Text

public class AgentLoader {
private static final String jarFilePath = "/Users/jiangxu/work/git/java-agent/target/java-agent-demo-1.0-SNAPSHOT.jar";
public static void loadAgent() {
String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
int p = nameOfRunningVM.indexOf('@');
String pid = nameOfRunningVM.substring(0, p);
try {
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(jarFilePath, "");
vm.detach();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
AgentLoader.loadAgent();
}
}

3 Instrumentation进阶方法

Premain和Agentmain提供了两种方式以供我们提前或者在JVM运行时进行一些操作,但是如何对JVM中的类进行监控甚至修改,首先就需要用到两个类:ClassFileTransformer和ClassDefinition。

3.1 ClassFileTransformer

ClassFileTransformer是一个接口,该接口有一个方法:

代码块

Plain Text

byte[] transform(  ClassLoader         loader,
String              className,
Class<?>            classBeingRedefined,
ProtectionDomain    protectionDomain,
byte[]              classfileBuffer) throws IllegalClassFormatException;

 

 

 

3.2 ClassDefinition

转载于:https://my.oschina.net/u/3705388/blog/3016833

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值