之前见过好多种-javaagent 参数,比如我们IDEA启动一个类的时候就会有好多的javaagent。 好像又叫探针技术,简单研究下其过程。
Java 5 中提供的 Instrument 包启动时往 Java 虚拟机中挂上一个用户定义的 hook 程序,可以在装入特定类的时候改变特定类的字节码,从而改变该类的行为。Instrument 包是在整个虚拟机上挂了一个钩子程序,每次装入一个新类的时候,都必须执行一遍这段程序,即使这个类不需要改变。一个核心类是sun.instrument.InstrumentationImpl, 这个类可以动态的增加转换器或者获取当前JVM加载的所有的类信息。
参考:
第一种: 使用 premain 可以在类第一次加载之前修改类信息,加载之后修改需要重新创建类加载器, premain是Java SE5开始就提供的代理方式。而且使用时必须在命令行指定代理jar,并且代理类必须在main方法前启动。
第二种: Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。
1. premain 使用
1. 简单使用
1. agent.MyAgent
package agent;
import java.lang.instrument.Instrumentation;
public class MyAgent {
/
JVM 在类加载前会调用到此函数
@param agentOps
@param inst
/
public static void premain(String agentOps, Instrumentation inst) {
System.out.println("agent.MyAgent.premain start ");
System.out.println(agentOps);
System.out.println(inst);
System.out.println("agent.MyAgent.premain end ");
}
}
2. 编写META-INF/MANIFEST.MF
Manifest-Version: 1.0
Can-Retransform-Classes: true
Premain-Class: agent.MyAgent
这个配置文件需要注意格式,如果之前打过jar 包应该会注意。 最后有个空行, 每个key后面的冒号 和 value 之间有个空格
3. 最后的目录结构如下:
$ ls -R
.:
agent/ META-INF/
./agent:
MyAgent.class
./META-INF:
MANIFEST.MF
4. 生成jar 包
D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./
已添加清单
正在添加: agent/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: agent/MyAgent.class(输入 = 754) (输出 = 415)(压缩了 44%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF
5. 新建测试类:
public class PlainTest {
public static void main(String【】 args) throws InterruptedException {
new PlainTest().test();
}
public void test() throws InterruptedException {
Thread.sleep(5 1000);
System.out.println("cn.qz.PlainTest.test\t" + 111222);
}
}
6. 编译运行测试:
$ java -javaagent:D:/agentjar/agent.jar PlainTest
agent.MyAgent.premain start
null
sun.instrument.InstrumentationImpl@5fe5c6f
agent.MyAgent.premain end
cn.qz.PlainTest.test 111222
测试传递参数: (可以传递参数给指定的方法, 方法内部也可以根据参数进行一些特殊的处理)
$ java -javaagent:D:/agentjar/agent.jar=key1=value1,key2=value2 PlainTest
agent.MyAgent.premain start
key1=value1,key2=value2
sun.instrument.InstrumentationImpl@5fe5c6f
agent.MyAgent.premain end
cn.qz.PlainTest.test 111222
7. 使用IDEA 的方式进行调试
同样的jar 包指定使用之前打的jar,和探针对应的类可以在DIEA 中使用java 文件进行调试。
(1) 目录结构
(2) agent.MyAgent
package agent;
import java.lang.instrument.Instrumentation;
public class MyAgent {
/
JVM 在类加载前会调用到此函数
@param agentOps
@param inst
/
public static void premain(String agentOps, Instrumentation inst) {
System.out.println("agent.MyAgent.premain XXX start ");
System.out.println(agentOps);
System.out.println(inst);
System.out.println("agent.MyAgent.premain XXX end ");
}
}
(3) 增加测试类
package cn.qz;
public class PlainTest {
public static void main(String【】 args) throws InterruptedException {
new PlainTest().test();
}
public void test() throws InterruptedException {
Thread.sleep(5 1000);
System.out.println("cn.qz.PlainTest.test\t" + 111222);
}
}
(4) 编辑增加代理 idea 中 Add VM Operations 增加参数: -javaagent:D:\agentjar\agent.jar
(5) 测试查看运行结果:
agent.MyAgent.premain XXX start
null
sun.instrument.InstrumentationImpl@1eb44e46
agent.MyAgent.premain XXX end
cn.qz.PlainTest.test 111222
(6) debug 到agent.MyAgent#premain 方法内部, 查看调用链:
2. premain 实现监测方法执行时间的操作
基于javassit 对字节码进行增强。
1. pom 增加
org.javassist
javassist
3.28.0-GA
2. agent.MyAgent 源码
package agent;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class MyAgent {
/
有该方法会优先执行该方法
@param agentOps
@param inst
*/
public static void premain(String agentOps, Instrumentation inst) {
System.out.println("====premain 方法执行");
System.out.println(agentOps);
System.out.println(inst);
System.out.println("====premain2 方法执行");
/
添加一个转换器, 字节码加载到虚拟机前会调用此类的transform 方法
/
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte【】 transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte【】 classfileBuffer) throws IllegalClassFormatException {
if (!className.startsWith("cn/qz")) {
return null;
}
System.out.println("ClassLoader : " + loader);
System.out.println("className : " + className);
//创建类,这是一个单例对象
ClassPool cp = ClassPool.getDefault();
//我们需要构建的类
try {
CtClass ctClass = cp.get(className.replace("/", "."));
CtMethod【】 declaredMethods = ctClass.getDeclaredMethods();
for (CtMethod method : declaredMethods) {
// 修改方法体来实现, 增加两个局部变量用于记录执行时间
method.addLocalVariable("startTimeAgent", CtClass.longType);
method.insertBefore("startTimeAgent = System.currentTimeMillis();");
method.addLocalVariable("methodNameAgent", cp.get(String.class.getName()));
method.insertBefore("methodNameAgent = \""//代码效果参考:http://www.jhylw.com.cn/211138523.html
+ method.getLongName() + "\";");method.insertAfter("System.out.println(methodNameAgent + \" exec time is :\" + (System.currentTimeMillis() - startTimeAgent) + \"ms\");");
}
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
});
}
}
3. 使用上面测试类进行测试查看日志
====premain 方法执行
null
sun.instrument.InstrumentationImpl@1eb44e46
====premain2 方法执行
ClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
className : cn/qz/PlainTest
cn.qz.PlainTest.test 111222
cn.qz.PlainTest.test() exec time is :5001ms
cn.qz.PlainTest.main(java.lang.String【】) exec time is :5002ms
4. 打包:
(1) 下载pom 依赖的jar 包, 进入项目目录(有pom.xml的目录),cmd执行如下命令
mvn dependency:copy-dependencies -DoutputDirectory=dependency_lib
(2) 构造目录结构:
$ ls -R
.:
agent/ dependency_lib/ META-INF/
./agent:
'MyAgent$1.class' MyAgent.class
./dependency_lib:
javassist-3.28.0-GA.jar
./META-INF:
MANIFEST.MF
(3) 修改MANIFEST.MF
Manifest-Version: 1.0
Can-Retransform-Classes: true
Class-Path: dependency_lib/javassist-3.28.0-GA.jar
Premain-Class: agent.MyAgent
(4) 替换编译后的class 文件
(5) 打包
D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./
已添加清单
正在添加: agent/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: agent/MyAgent$1.class(输入 = 3142) (输出 = 1533)(压缩了 51%)
正在添加: agent/MyAgent.class(输入 = 941) (输出 = 524)(压缩了 44%)
正在添加: dependency_lib/(输入 = 0) (输出 = 0)(存储了 0%)
正在添加: dependency_lib/javassist-3.28.0-GA.jar(输入 = 851531) (输出 = 794719)(压缩了 6%)
正在忽略条目META-INF/
正在忽略条目META-INF/MANIFEST.MF
(6) 编写测试类编译后测试:
package cn.qz;
import java.util.concurrent.CountDownLatch;
public class PlainTest {
public static void main(String【】 args) throws InterruptedException {
new PlainTest().test();
CountDownLatch countDownLatch = new CountDownLatch(1);
countDownLatch.await();
}
public void test() throws InterruptedException {
Thread.sleep(5 1000);
System.out.println("cn.qz.PlainTest.test\t" + 111222);
}
}
1》测试:
D:\agentjar>java -javaagent:D:\agentjar\agent.jar cn.qz.PlainTest
====premain 方法执行
null
sun.instrument.InstrumentationImpl@6979e8cb
====premain2 方法执行
ClassLoader : jdk.internal.loader.ClassLoaders$AppClassLoader@383534aa
className : cn/qz/PlainTest
cn.qz.PlainTest.test 111222
cn.qz.PlainTest.test() exec time is :5001ms
2》 使用arthas 实时反编译查看生成的类信息
/
Decompiled with CFR.
/
package cn.qz;
import java.util.concurrent.CountDownLatch;
public class PlainTest {
/
WARNING - void declaration
/
public static void main(String【】 stringArray) throws InterruptedException {
void var2_1;
void var4_2;
long startTimeAgent = System.currentTimeMillis();
String methodNameAgent = "cn.qz.PlainTest.main(java.lang.String【】)";
new PlainTest().test();
CountDownLatch countDownLatch = new CountDownLatch(1);
/10/ countDownLatch.await();
Object var6_4 = null;
System.out.println(new StringBuffer().append((String)var4_2).append(" exec time is :").append(System.currentTimeMillis() - var2_1).append("ms").toString());
}
/
WARNING - void declaration
/
public void test() throws InterruptedException {
void var1_1;
void var3_2;
long startTimeAgent = System.currentTimeMillis();
String methodNameAgent = "cn.qz.PlainTest.test()";
/14/ Thread.sleep(5000L);
/15/ System.out.println("cn.qz.PlainTest.test\t111222");
Object var5_3 = null;
System.out.println(new StringBuffer().append((String)var3_2).append(" exec time is :").append(System.currentTimeMillis() - var1_1).append("ms").toString());
}
}</