以 JVM参数 (-javaagent)的方式启动,在Java程序的main方法执行之前执行
package me.zhongmingmao;
public class MyAgent {
// JVM能识别的premain方法接收的是字符串类型的参数,并非类似main方法的字符串数组
public static void premain(String args) {
System.out.println("premain");
}
}
# 写入两行数据,最后一行为空行
$ echo 'Premain-Class: me.zhongmingmao.MyAgent
' > manifest.txt
$ tree
.
├── manifest.txt
└── me
└── zhongmingmao
└── MyAgent.java
$ javac me/zhongmingmao/MyAgent.java $ jar cvmf manifest.txt myagent.jar me/ 已添加清单 正在添加: me/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: me/zhongmingmao/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: me/zhongmingmao/MyAgent.class(输入 = 399) (输出 = 285)(压缩了 28%) 正在添加: me/zhongmingmao/MyAgent.java(输入 = 142) (输出 = 114)(压缩了 19%)
package helloworld;
import java.util.concurrent.TimeUnit;
public class HelloWorld {
public static void main(String[] args) throws InterruptedException {
System.out.println("Hello World");
TimeUnit.MINUTES.sleep(1);
}
}
$ javac helloworld/HelloWorld.java $ java -javaagent:myagent.jar helloworld.HelloWorld premain Hello World
package me.zhongmingmao;
public class MyAgent {
public static void agentmain(String args) {
System.out.println("agentmain");
}
}
# 改为Agent-Class
$ echo 'Agent-Class: me.zhongmingmao.MyAgent
' > manifest.txt
$ tree
.
├── manifest.txt
└── me
└── zhongmingmao
└── MyAgent.java
$ javac me/zhongmingmao/MyAgent.java $ jar cvmf manifest.txt myagent.jar me/ 已添加清单 正在添加: me/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: me/zhongmingmao/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: me/zhongmingmao/MyAgent.class(输入 = 401) (输出 = 285)(压缩了 28%) 正在添加: me/zhongmingmao/MyAgent.java(输入 = 146) (输出 = 115)(压缩了 21%)
import com.sun.tools.attach.VirtualMachine;
public class AttachTest {
public static void main(String[] args) throws Exception {
if (args.length <= 1) {
System.out.println("Usage: java AttachTest <PID> /PATH/TO/AGENT.jar");
return;
}
String pid = args[0];
String agent = args[1];
// Attach API
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agent);
}
}
编译AttachTest
# 指定classpath $ javac -cp ~/.sdkman/candidates/java/current/lib/tools.jar AttachTest.java
$ java helloworld.HelloWorld $ jps 23386 HelloWorld 23387 Jps
$ java -cp ~/.sdkman/candidates/java/current/lib/tools.jar:. AttachTest 23386 PATH_TO_AGENT/myagent.jar
# HelloWorld进程继续输出agentmain Hello World agentmain
package me.zhongmingmao;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class MyAgent {
public static void premain(String args, Instrumentation instrumentation) {
// 通过instrumentation来注册类加载事件的拦截器(实现ClassFileTransformer.transform)
instrumentation.addTransformer(new MyTransformer());
}
static class MyTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// 返回的byte数组,代表更新后的字节码
// 当transform方法返回时,JVM会使用返回的byte数组来完成接下来的类加载工作
// 如果transform方法返回null或者抛出异常,JVM将使用原来的byte数组来完成类加载工作
// 基于类加载事件的拦截功能,可以实现字节码注入(Bytecode instrumentation),往正在被加载的类插入额外的字节码
System.out.printf("Loaded %s: 0x%X%X%X%X/n", className,
classfileBuffer[0], classfileBuffer[1], classfileBuffer[2], classfileBuffer[3]);
return null;
}
}
}
$ java -javaagent:myagent.jar helloworld.HelloWorld ... Loaded helloworld/HelloWorld: 0xCAFEBABE Hello World ... Loaded java/lang/Shutdown: 0xCAFEBABE Loaded java/lang/Shutdown$Lock: 0xCAFEBABE
通过ASM注入字节码可参考 Instrumenting Java Bytecode with ASM
package me.zhongmingmao;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class MyAgent {
public static void premain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new MyTransformer());
}
static class MyTransformer implements ClassFileTransformer, Opcodes {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// 将classfileBuffer转换为ClassNode
ClassReader classReader = new ClassReader(classfileBuffer);
ClassNode classNode = new ClassNode(ASM7);
classReader.accept(classNode, ClassReader.SKIP_FRAMES);
// 遍历ClassNode的MethodNode节点,即构造器和方法
for (MethodNode methodNode : classNode.methods) {
// 在main方法入口处注入System.out.println("Hello Instrumentation");
if ("main".equals(methodNode.name)) {
InsnList instrumentation = new InsnList();
instrumentation.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
instrumentation.add(new LdcInsnNode("Hello, Instrumentation!"));
instrumentation.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
methodNode.instructions.insert(instrumentation);
}
}
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
classNode.accept(classWriter);
return classWriter.toByteArray();
}
}
}
编译MyAgent
$ javac -cp PATH_TO_ASM/asm-7.0.jar:PATH_TO_ASM_TREE/asm-tree-7.0.jar me/zhongmingmao/MyAgent.java
$ java -javaagent:myagent.jar -cp PATH_TO_ASM/asm-7.0.jar:PATH_TO_ASM_TREE/asm-tree-7.0.jar:. helloworld.HelloWorld Hello, Instrumentation! Hello World
package me.zhongmingmao;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class MyProfiler {
// 统计每个类所新建实例的数目
public static ConcurrentHashMap<Class<?>, AtomicInteger> data = new ConcurrentHashMap<>();
public static void fireAllocationEvent(Class<?> klass) {
data.computeIfAbsent(klass, kls -> new AtomicInteger()).incrementAndGet();
}
public static void dump() {
data.forEach((kls, counter) -> System.err.printf("%s: %d/n", kls.getName(), counter.get()));
}
static {
Runtime.getRuntime().addShutdownHook(new Thread(MyProfiler::dump));
}
}
package me.zhongmingmao;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class MyAgent {
public static void premain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new MyTransformer());
}
static class MyTransformer implements ClassFileTransformer, Opcodes {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.startsWith("java") ||
className.startsWith("javax") ||
className.startsWith("jdk") ||
className.startsWith("sun") ||
className.startsWith("com/sun") ||
className.startsWith("me/zhongmingmao")) {
// Skip JDK classes and profiler classes
// 避免循环引用,从而导致StackOverflowException
return null;
}
ClassReader classReader = new ClassReader(classfileBuffer);
ClassNode classNode = new ClassNode(ASM7);
classReader.accept(classNode, ClassReader.SKIP_FRAMES);
for (MethodNode methodNode : classNode.methods) {
// 遍历方法内的指令
for (AbstractInsnNode node : methodNode.instructions.toArray()) {
if (node.getOpcode() == NEW) {
// 在每条new指令后插入对fireAllocationEvent方法的调用
TypeInsnNode typeInsnNode = (TypeInsnNode) node;
InsnList instrumentation = new InsnList();
instrumentation.add(new LdcInsnNode(Type.getObjectType(typeInsnNode.desc)));
instrumentation.add(new MethodInsnNode(INVOKESTATIC, "me/zhongmingmao/MyProfiler", "fireAllocationEvent", "(Ljava/lang/Class;)V", false));
methodNode.instructions.insert(node, instrumentation);
}
}
}
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
classNode.accept(classWriter);
return classWriter.toByteArray();
}
}
}
public class ProfilerMain {
public static void main(String[] args) {
String s = "";
for (int i = 0; i < 10; i++) {
s = new String("" + i);
}
Integer i = 0;
for (int j = 0; j < 20; j++) {
i = new Integer(j);
}
}
}
$ java -javaagent:myagent.jar -cp PATH_TO_ASM/asm-7.0.jar:PATH_TO_ASM_TREE/asm-tree-7.0.jar:. ProfilerMain java.lang.StringBuilder: 10 java.lang.String: 10 java.lang.Integer: 20