JVM字节码执行

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出栈 ,如果栈顶value0则跳转到(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]详细

猜你喜欢

转载自my.oschina.net/u/3512041/blog/1823182