结论:attach有使两个jvm相互通信的能力,使用socket进行通信。–查了源码。
一:创建工程 attachdemo
File–>New–>Project…–>左边选择"Maven"–>工程名为:attachdemo 填写各项数据后–> Finish
二:实际代码
2.1.增强类GdAgentMain.java
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import static org.objectweb.asm.Opcodes.ASM7;
/**
* 功能:对 GdMyTestMain.class进行字节码增加,即修改返回值60。
*
* 动态 Attach 的 agent 会执行 agentmain 方法,而不是 premain 方法。
*/
public class GdAgentMain {
public static class MyGdMethodVisitor extends AdviceAdapter {
protected MyGdMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
super(ASM7, mv, access, name, desc);
}
@Override
protected void onMethodEnter() {
// 在方法开始插入 return 60;
mv.visitIntInsn(BIPUSH, 60);//BIPUSH:当int取值-128~127时,JVM采用bipush指令将常量压入栈中
mv.visitInsn(IRETURN); //visitInsn、visitVarInsn、visitMethodInsn等以Insn结尾的方法可以添加方法实现的字节码
}
}
public static class MyGdClassVisitor extends ClassVisitor {
public MyGdClassVisitor(ClassVisitor classVisitor) {
super(ASM7, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
// 只转换 GetAge 方法
if ("GetAge".equals(name)) {
return new MyGdMethodVisitor(mv, access, name, descriptor);
}
return mv;
}
}
public static class MyGdClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
if (!"GdMyTestMain".equals(className)) return bytes;
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new MyGdClassVisitor(cw); //使用自己的访问器
cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return cw.toByteArray();
}
}
public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println("agentmain called");
inst.addTransformer(new MyGdClassFileTransformer(), true); //1.把自己的实现类变形器加载进来。
Class classes[] = inst.getAllLoadedClasses();
for (int i = 0; i < classes.length; i++) {
if (classes[i].getName().equals("GdMyTestMain")) { //2.过滤GdMyTestMain的类。
System.out.println("Reloading: " + classes[i].getName());
inst.retransformClasses(classes[i]); // 3.重新加载GdMyTestMain类。
break;
}
}
}
}
2.2 pom.xml 的内容
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>JavaAttachAgent</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>7.1</version>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8.0</version>
<scope>system</scope>
<systemPath>/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<finalName>gd-my-attach-agent</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Agent-Class>GdAgentMain</Agent-Class>
<Premain-Class>GdAgentMain</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.ow2.asm</pattern>
<shadedPattern>me.ya.agent.hidden.org.ow2.asm</shadedPattern>
</relocation>
<relocation>
<pattern>org.objectweb.asm</pattern>
<shadedPattern>me.ya.agent.hidden.org.objectweb.asm</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<!-- 打jar包时,排除下面两个java文件打包进去 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<excludes>
<exclude>GdMyTestMain.java</exclude>
<exclude>GdMyAttachMain.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.3 GdMyAttachMain.java 的代码
import com.sun.tools.attach.VirtualMachine;
public class GdMyAttachMain {
/**
* PID 通过 jps查看对应的进程ID-->GdMyTestMain
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
String PID="8127";
// String PID=args[0];
System.out.println(PID);
VirtualMachine vm = VirtualMachine.attach(PID);
try {
vm.loadAgent("/Users/lichunmei/Documents/workspace/study/attachdemo/target/gd-my-attach-agent.jar");
} finally {
vm.detach();
}
}
}
2.4 GdMyTestMain.java的代码
import java.util.concurrent.TimeUnit;
public class GdMyTestMain {
public static void main(String[] args) throws InterruptedException {
while (true) {
System.out.println(GetAge());
TimeUnit.SECONDS.sleep(4);
}
}
public static int GetAge() {
return 100; // 修改后 return 60;
}
}
三:运行测试步骤
1. 只把GdAgentMain.java文件打包到 gd-my-attach-agent.jar 里。在pom.xml添加了排除另两个类。
> mvn clean package
2. 直接在idea 中运行 GdMyTestMain.java的 main.
jps 查到其进程号,假设为 7617 。
3. 修改 GdMyAttachMain.java
--里修改加载的路径为gd-my-attach-agent.jar的全路径。
--PID="8127"
-在idea中运行 GdMyAttachMain.java 的main,执行完就结束。
4.看到 GdMyTestMain 的输出从100 到 60 .
结果如下:
100
100
100
100
objc[8127]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/bin/java (0x10fad44c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1197964e0). One of the two will be used. Which one is undefined.
agentmain called
Reloading: GdMyTestMain
60
60
60
60
60
60
备注:上面抛了objc[7816]:.........的问题,据说"这是Mac版IDEA自身的BUG,你可以忽略它!!"
总结:字节码增加是针对目标jvm增加。最终依然只有目标一个jvm。