JVM字节码执行
大纲:
n javap
n 简单的字节码执行过程
n 常用的字节码
n 使用ASM生成Java字节码
n JIT及其相关参数
javap
n javap
– class文件反汇编工具
public class Calc { public int calc() { int a = 500; int b = 200; int c = 50; return (a + b) / c; } } |
– javap –verbose[z1] Calc 生成Calc类的字节码文件的反汇编文件。
public int calc(); Code: Stack=2, Locals=4, Args_size=1 0: sipush 500 3: istore_1 4: sipush 200 7: istore_2 8: bipush 50 10: istore_3 11: iload_1 12: iload_2 13: iadd 14: iload_3 15: idiv 16: ireturn } |
简单的字节码执行过程
代码对应字节码执行过程:
public class Calc {
public int calc() {
int a = 500;
int b = 200;
int c = 50;
return (a + b) / c;
}
}
public class Calc {
public int calc() {
int a = 500;
int b = 200;
int c = 50;
return (a + b) / c;
}
}
public class Calc {
public int calc() {
int a = 500;
int b = 200;
int c = 50;
return (a + b) / c;
}
}
public class Calc {
public int calc() {
int a = 500;
int b = 200;
int c = 50;
return (a + b) / c;
}
}
public class Calc {
public int calc() {
int a = 500;
int b = 200;
int c = 50;
return (a + b) / c;
}
}
public class Calc {
public int calc() {
int a = 500;
int b = 200;
int c = 50;
return (a + b) / c;
}
}
字节码文件:
字节码指令为一个byte整数
字节码指令为一个byte整数
_nop = 0, // 0x00 _aconst_null = 1, // 0x01 _iconst_0 = 3, // 0x03 _iconst_1 = 4, // 0x04 _dconst_1 = 15, // 0x0f _bipush = 16, // 0x10 _iload_0 = 26, // 0x1a _iload_1 = 27, // 0x1b _aload_0 = 42, // 0x2a _istore = 54, // 0x36 _pop = 87, // 0x57 _imul = 104, // 0x68 _idiv = 108, // 0x6c |
void setAge(int) 方法的字节码
public void setAge(int age) { this.age = age; } |
n void setAge(int) 方法的字节码
n 2A 1B B5 00 20 B1
n 2A _aload_0
– 无参
– 将局部变量slot0(第0个槽位的值) 作为引用 压入操作数栈
n 1B _iload_1
– 无参
– 将局部变量slot1(第一个槽位的值) 作为整数 压入操作数栈
n B5 _putfield
– 设置对象中字段的值
– 参数为2bytes (00 20) (指明了字段)
n 指向常量池的引用
n Constant_Fieldref
n 此处为User.age
– 弹出栈中2个对象:objectref, value
– 将栈中的value赋给objectref的给定字段
n B1 _return
常用的字节码
常量入栈
n 常量入栈
– aconst_null null对象入栈
– iconst_m1 int常量-1入栈
– iconst_0 int常量0入栈
– iconst_5
– lconst_1 long常量1入栈
– fconst_1 float 1.0入栈
– dconst_1 double 1.0 入栈
– bipush 8位带符号整数入栈
– sipush 16位带符号整数入栈
– ldc 常量池中的项入栈
– ldc index index u1为指向常量池的有效无符号8位索引
局部变量压栈
n 局部变量压栈
– xload(x为i l f d a){将某种类型的数据压栈}
• 分别表示int,long,float,double,object ref(对象的引用)
– xload_n(n为0 1 2 3){将局部变量表中槽位索引为n的某种类型的数据压栈}
– xaload(x为i l f d a b c s)
• 分别表示int, long, float, double, obj ref ,byte,char,short
• 从数组中取得给定索引的值,将该值压栈
• iaload
– 执行前,栈:..., arrayref, index
– 它取得arrayref所在数组的index的值,并将值压栈
– 执行后,栈:..., value
出栈,装载入局部变量表
n 出栈,装载入局部变量表
– xstore(x为i l f d a)
• 出栈,存入局部变量表
– xstore_n(n 0 1 2 3)
• 出栈,将值存入局部变量表索引为n的槽位
– xastore(x为i l f d a b c s)
• 将值存入数组中
• iastore
– 执行前,栈:...,arrayref, index, value
– 执行后,栈:...
– 将value存入arrayref[index]
通用栈操作(无类型)
n 通用栈操作(无类型)
– nop
不做任何操作
– pop
• 弹出栈顶1个字长
– dup
• 复制栈顶1个字长,复制内容压入栈
类型转化
n 类型转化
– i2l
– i2f
– l2i
– l2f
– l2d
– f2i
– f2d
– d2i
– d2l
– d2f
– i2b
– i2c
– i2s
n i2l
– 将int转为long
– 执行前,栈:..., value
– 执行后,栈:...,result.word1,result.word2
– 弹出int,扩展为long,并入栈
整数运算和浮点数运算
基本工作流程都是,弹出栈顶2个数,做运算,结果入栈
整数运算
n 整数运算
– iadd
– ladd
– isub
– lsub
– idiv
– ldiv
– imul
– lmul
– iinc
浮点运算
n 浮点运算
– fadd
– dadd
– fsub
– dsub
– fdiv
– ddiv
– fmul
– dmul
对象操作指令
n 对象操作指令
– new(创建类实例的指令)
– 访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者成为实例变量)的指令
n getfield(取出对象中字段的值)
n putfield(设置对象中字段的值)
n getstatic
n putstatic
条件控制指令
n 条件控制指令
– ifeq 如果为0,则跳转
– ifne 如果不为0,则跳转
– iflt 如果小于0 ,则跳转
– ifge 如果大于0,则跳转
– if_icmpeq 如果两个int相同,则跳转
n ifeq
– 参数 byte1,byte2
– value出栈 ,如果栈顶value为0则跳转到(byte1<<8)|byte2
– 执行前,栈:...,value
– 执行后,栈:...
方法调用指令
n 方法调用指令
– invokevirtual
– invokespecial
通常根据引用的类型选择方法,而不是对象的类来选择!即它使用静态绑定而不是动态绑定。
对应私有方法 超类方法 实例初始化方法
– invokestatic
– invokeinterface
– xreturn(x为 i l f d a 或为空)
x为空,表示方法返回void
使用ASM生成Java字节码
ASM
n ASM
– Java字节码操作框架
– 可以用于修改现有类或者动态产生新类
– 用户
• AspectJ
• Clojure
• Ecplise
• spring
• cglib
– hibernate
Hello world程序
public static void main(String[] args) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); cw.visit(V1_7, ACC_PUBLIC, "Example", null, "java/lang/Object", null); MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mw.visitVarInsn(ALOAD, 0); // this 入栈 mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mw.visitInsn(RETURN); mw.visitMaxs(0, 0); mw.visitEnd(); mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mw.visitLdcInsn("Hello world!"); mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); mw.visitInsn(RETURN); mw.visitMaxs(0, 0); mw.visitEnd(); byte[] code = cw.toByteArray(); AsmHelloWorld loader = new AsmHelloWorld(); Class exampleClass = loader.defineClass("Example", code, 0, code.length); exampleClass.getMethods()[0].invoke(null, new Object[] { null }); } |
模拟实现AOP字节码织入
n 模拟实现AOP字节码织入
– 在函数开始部分或者结束部分嵌入字节码
– 可用于进行鉴权、日志等
public class Account { public void operation() { System.out.println("operation...."); } } |
在操作前加上鉴权或者日志
我们要嵌入的内容
public class SecurityChecker { public static boolean checkSecurity() { System.out.println("SecurityChecker.checkSecurity ..."); return true; } } |
使用ASM实现代码:
class AddSecurityCheckClassAdapter extends ClassVisitor { public AddSecurityCheckClassAdapter(ClassVisitor cv) { super(Opcodes.ASM5, cv); } // 重写 visitMethod,访问到 "operation" 方法时, // 给出自定义 MethodVisitor,实际改写方法内容 public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { // 对于 "operation" 方法 if (name.equals("operation")) { // 使用自定义 MethodVisitor,实际改写方法内容 wrappedMv = new AddSecurityCheckMethodAdapter(mv); } } return wrappedMv; } } |
class AddSecurityCheckMethodAdapter extends MethodVisitor { public AddSecurityCheckMethodAdapter(MethodVisitor mv) { super(Opcodes.ASM5, mv); } public void visitCode() { visitMethodInsn(Opcodes.INVOKESTATIC, "geym/jvm/ch10/asm/SecurityChecker", "checkSecurity", "()Z"); super.visitCode(); } } |
public class Generator { public static void main(String args[]) throws Exception { ClassReader cr = new ClassReader("geym.jvm.ch10.asm.Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); AddSecurityCheckClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); File file = new File("bin/geym/jvm/ch10/asm/Account.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(data); fout.close(); } } |
JIT及其相关参数
n 字节码执行性能较差,所以可以对于热点代码编译成机器码再执行,在运行时的编译,叫做JIT Just-In-Time
n JIT的基本思路是,将热点代码,就是执行比较频繁的代码,编译成机器码。
-XX:CompileThreshold=1000
-XX:+PrintCompilation
JIT 编译参数
JIT(Just-In-Time)编译器, 可以在运行时将字节码编译成本地代码,从而提升函数的执行效率。
-XX:CompileThreshold为 JIT编译的阈值, 当函数的调用次数超过-XX:CompileThreshold时,JIT就将字节码编译成本地机器码。
在Client 模式下, XX:CompileThreshold 的取值为1500;
在Server 模式下, 取值是 10000.
JIT编译完成后, JVM便会使用本地代码代替原来的字节码解释执行。
为了内合理的设置JIT编译的阈值, 可以使用-XX:_CITime打印出JIT编译的耗时, 也可以使用-XX:+PrintCompilation 打印出JIT编译的信息。
JIT示例:
代码:
public class JITTest { public static void met(){ int a=0,b=0; b=a+b; }
public static void main(String[] args) { for(int i=0;i<1000;i++){ met(); } } } |
运行参数:
-XX:CompileThreshold=1000
-XX:+PrintCompilation
打印输出:
输出 56 1 java.lang.String::hashCode (55 bytes) 56 2 java.lang.String::equals (81 bytes) 57 3 java.lang.String::indexOf (70 bytes) 60 4 java.lang.String::charAt (29 bytes) 61 5 java.lang.String::length (6 bytes) 61 6 java.lang.String::lastIndexOf (52 bytes) 61 7 java.lang.String::toLowerCase (472 bytes) 67 8 geym.jvm.ch2.jit.JITTest::met (9 bytes) |
字节码执行参数:
n -Xint
– 解释执行
n -Xcomp
– 全部编译执行
n -Xmixed
– 默认的,混合(字节码解释执行+字节码编译成机器码解释执行)执行
[z1]详细