一般情况下,使用 JavaRebel 时都配置两个 JVM 参数:-noverify 和 -javaagent
一、-javaagent 参数
这个参数是 JDK5 引入的,可以通过 java -h 查看其帮助信息
// 省略
-javaagent:<jarpath>[=<options>]
load Java programming language agent, see java.lang.instrument
// 省略
通过使用 -javaagent 参数,用户可以在执行 main 函数前执行指定 javaagent 包中的特定代码,甚至可以动态的修改替换类中代码。
javaagent 的代码与你的 main 方法在同一个 JVM 中运行,并被同一个 system classloader 装载,被同一的安全策略(security policy) 和上下文(context)所管理。
如何写一个 javaagent 程序呢?实现很简单,只需要在类中实现 premain 接口:
public static void premain(String agentArgs, Instrumentation inst)
例如可以动态将下面方法中的 true 改为 false
package testagent;
public class TestAgent {
public boolean isOK() {
return true;
}
public static void main(String[] args) {
System.out.println(new TestAgent().isOK());
}
}
package testagent;
import java.lang.instrument.Instrumentation;
public class MyTestAgent {
public static void premain(String agentArgs,Instrumentation inst) {
inst.addTransformer(new MyTestTransformer());
}
}
package testagent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class MyTestTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if(!className.equals("testagent/TestAgent"))
return null;
for(int i=0; i<classfileBuffer.length; i++) {
if(classfileBuffer[i] == (byte)0x04
&& classfileBuffer[i+1] == (byte)0xAC) {
classfileBuffer[i] = (byte)0x03;
}
}
return classfileBuffer;
}
}
// 注意:提前创建 META-INF/MANIFEST.MF 文件
jar -cvfm myagent.jar META-INF/MANIFEST.MF testagent/MyTestAgent.class testagent/MyTestTransformer.class
其中的 MANIFEST.MF 的内容如下:
Manifest-Version: 1.0
Created-By: 1.6.0_16 (Sun Microsystems Inc.)
Premain-Class: testagent.MyTestAgent
而运行方法则如下:
java -javaagent:myagent.jar testagent.TestAgent
二、-noverify 参数
通过使用 -noverify 参数,关闭 Java 字节码的校验功能。
当 ClassLoader 加载的Java 字节码时,字节码首先接受校验器(verifier)的校验。校验器负责检查那些指令无法执行的明显的破坏性的操作。校验器执行的检查操作:
1、变量要在使用之前进行初始化。
2、方法调用与对象应用类型之间要匹配。
3、访问私有数据和方法的规则没有被违反。
4、对本地变量的访问都在运行时堆栈内。
5、运行时堆栈没有溢处。
下面实际演示一下,手动修改 class 文件前后的情况。测试类文件如下:
package testverify;
public class TestVerify {
public int test() {
int i = 0, j = 1;
return i+j;
}
public static void main(String[] args) {
System.out.println(new TestVerify().test());
}
}
执行: java testverify.TestVerify
结果:1
手动将十六进制代码 03 3C 04 3D 1B 1C 60 AC 部分 3D 改成 3C ,即从 istore_2 改为 istore_1 ,局部变量 1 被初始化两次,而局部变量 2 未被初始化。
执行:java testverify.TestVerify
结果:Exception in thread "main" java.lang.VerifyError: (class: testverify/TestVerify,
method: test signature: ()I) Accessing value from uninitialized register 2
Could not find the main class: testverify.TestVerify. Program will exit.
执行:java -noverify testverify.TestVerify
结果:1