java instrument 初探

本文介绍如何使用Java 1.5引入的java.lang.instrument包创建一个Java Agent,通过字节码增强技术实现实时监控方法执行时间的功能。这种方式能够使监控代码与应用程序完全隔离。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java在1.5引入java.lang.instrument,你可以由此实现一个java agent,通过此agent来修改类的字节码即改变一个类。本文中,会通过java instrument 实现一个简单的profiler。当然instrument并不限于profiler,instrument可以做很多事情,

它类似一种更低级,更松耦合的AOP,可以从底层来改变一个类的行为,你可以由此产生无限的遐想。

接下来要做的事情,就是计算一个方法所花的时间,通常我们会在代码这么写:
在方法开始开头加入long stime = System.nanoTime();
在方法结尾通过System.nanoTime()-stime得出方法所花时间,你不得不在你想监控的每个方法中写入重复的代码,
好一点的情况,你可以用AOP来干这事,但总是感觉有点别扭,这种profiler的代码还是打包在你的项目中,

java instrument使得这更干净。

1) 写agent类
package org.toy; import java.lang.instrument.Instrumentation; import java.lang.instrument.ClassFileTransformer; public class PerfMonAgent { static private Instrumentation inst = null; /** * This method is called before the application’s main-method is called, * when this agent is specified to the Java VM. **/ public static void premain(String agentArgs, Instrumentation _inst) { System.out.println("PerfMonAgent.premain() was called."); // Initialize the static variables we use to track information. inst = _inst; // Set up the class-file transformer. ClassFileTransformer trans = new PerfMonXformer(); System.out.println("Adding a PerfMonXformer instance to the JVM."); inst.addTransformer(trans); } }

2)写ClassFileTransformer类
package org.toy; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtBehavior; import javassist.CtClass; import javassist.NotFoundException; import javassist.expr.ExprEditor; import javassist.expr.MethodCall; public class PerfMonXformer implements ClassFileTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { byte[] transformed = null; System.out.println("Transforming " + className); ClassPool pool = ClassPool.getDefault(); CtClass cl = null; try { cl = pool.makeClass(new java.io.ByteArrayInputStream( classfileBuffer)); if (cl.isInterface() == false) { CtBehavior[] methods = cl.getDeclaredBehaviors(); for (int i = 0; i < methods.length; i++) { if (methods[i].isEmpty() == false) { doMethod(methods[i]); } } transformed = cl.toBytecode(); } } catch (Exception e) { System.err.println("Could not instrument " + className + ", exception : " + e.getMessage()); } finally { if (cl != null) { cl.detach(); } } return transformed; } private void doMethod(CtBehavior method) throws NotFoundException, CannotCompileException { // method.insertBefore("long stime = System.nanoTime();"); // method.insertAfter("System.out.println(/"leave "+method.getName()+" and time:/"+(System.nanoTime()-stime));"); method.instrument(new ExprEditor() { public void edit(MethodCall m) throws CannotCompileException { m .replace("{ long stime = System.nanoTime(); $_ = $proceed($$); System.out.println(/"" + m.getClassName()+"."+m.getMethodName() + ":/"+(System.nanoTime()-stime));}"); } }); } }

上面两个类就是agent的核心了,jvm启动时并会在应用加载前会调用 PerfMonAgent.premain, 然后PerfMonAgent.premain中实例化了一个定制的ClassFileTransforme即 PerfMonXformer
并通过inst.addTransformer(trans);把PerfMonXformer的实例加入Instrumentation实例(由jvm传入),这就使得应用中的类加载的时候, PerfMonXformer.transform都会被调用,你在此方法中
可以改变加载的类,真的有点神奇,为了改变类的字节码,我使用了jboss的javassist,虽然你不一定要这么用,但jboss的javassist真的很强大,让你很容易的改变类的字节码。在上面的方法中
我通过改变类的字节码,在每个类的方法入口中加入了long stime = System.nanoTime();,在方法的出口加入了System.out.println("methodClassName.methodName:"+(System.nanoTime()-stime));

3) 打包agent
对于agent的打包,有点讲究,
3.1)
jar的META-INF/MANIFEST.MF加入Premain-Class: xx, xx在此语境中就是我们的agent类,即org.toy.PerfMonAgent
3.2)
如果你的agent类引入别的包,需使用Boot-Class-Path: xx,xx在此语境中就是上面提到的jboss javassit 即 /home/pwlazy/.m2/repository/javassist/javassist/3.8.0 .GA/javassist-3.8.0.GA.jar

下面附上maven 的pom
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.toy</groupId> <artifactId>toy-inst</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>toy-inst</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.8.0.GA</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.2</version> <configuration> <archive> <manifestEntries> <Premain-Class>org.toy.PerfMonAgent</Premain-Class> <Boot-Class-Path>/home/pwlazy/.m2/repository/javassist/javassist/3.8.0.GA/javassist-3.8.0.GA.jar</Boot-Class-Path> </manifestEntries> </archive> </configuration> </plugin> <plugin> <artifactId>maven-compiler-plugin </artifactId > <configuration> <source> 1.6 </source > <target> 1.6 </target> </configuration> </plugin> </plugins> </build> </project>

最终打成一个包toy-inst-1.0-SNAPSHOT.jar


4)打包应用
随便写个应用

package org.toy; public class App { public static void main(String[] args) { new App().test(); } public void test() { System.out.println("Hello World!!"); } }

最终打成一个包toy-1.0-SNAPSHOT.jar

5)执行命令

java -javaagent:target/toy-inst-1.0-SNAPSHOT.jar -cp /home/pwlazy/work/projects/toy/target/toy-1.0-SNAPSHOT.jar org.toy.App

java选项中有-javaagent:xx,xx就是你的agent jar,java通过此选项加载agent,由agent来监控classpath下的应用。

最后的执行结果:
PerfMonAgent.premain() was called. Adding a PerfMonXformer instance to the JVM. Transforming org/toy/App Hello World!! java.io.PrintStream.println:314216 org.toy.App.test:540082 Transforming java/lang/Shutdown Transforming java/lang/Shutdown$Lock java.lang.Shutdown.runHooks:29124 java.lang.Shutdown.sequence:132768

我们由执行结果可以看出执行顺序以及通过改变org.toy.App的字节码加入监控代码确实生效了。

你也可以发现通过instrment实现agent是的监控代码和应用代码完全隔离了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值