天天看點

JVM - 位元組碼執行

此部落格為煉數成金JVM教程第十一課

目錄

  1. javap
  2. 簡單的位元組碼執行過程
  3. 常用的位元組碼
  4. 使用ASM生成java位元組碼
  5. JIT及其相關參數

javap

     class 檔案反編譯工具

有如下代碼:

public class Calc{
    public int calc(){

        int a = ;
        int b = ;
        int c = ;
        return (a+b)/c;
    }

}
           

用javap 反編譯

javac Calc.java
javap -verbose Calc
           

結果:

Classfile /C:/Users/Administrator/Desktop/code/Calc.class
  Last modified --; size  bytes
  MD5 checksum c37bb8f7e29890883f8a5807
  Compiled from "Calc.java"
public class Calc
  minor version: 
  major version: 
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // Calc
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               calc
   #9 = Utf8               ()I
  #10 = Utf8               SourceFile
  #11 = Utf8               Calc.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               Calc
  #14 = Utf8               java/lang/Object
{
  public Calc();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=, locals=, args_size=
         : aload_0
         : invokespecial #1                  // Method java/lang/Object."<init>":()V
         : return
      LineNumberTable:
        line : 

  public int calc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=, locals=, args_size=
         : sipush        
         : istore_1
         : sipush        
         : istore_2
         : bipush        
        : istore_3
        : iload_1
        : iload_2
        : iadd
        : iload_3
        : idiv
        : ireturn
      LineNumberTable:
        line : 
        line : 
        line : 
        line : 
}
SourceFile: "Calc.java"
           

具體我們看

public int calc();
    Code:
      stack=, locals=, args_size=
         : sipush        
         : istore_1
         : sipush        
         : istore_2
         : bipush        
        : istore_3
        : iload_1
        : iload_2
        : iadd
        : iload_3
        : idiv
        : ireturn
}
           

其中 1 3 4 … 16,對于位元組碼來說是偏移量,廣義上來說可以了解為行号

這些行代表的是:方法運作時需要運作的指令

上述位元組碼執行過程

我們需要關注:

    程式計數器:每個線程都有一個單獨的計數器指向目前正在執行的    指令的位置

    局部變量表

    操作數棧

執行 sipush

  該方法為執行個體方法,this作為第一個參數放在局部變量表,

  sipush 把500 放入到操作數棧

執行istore_1

  從操作數棧彈出一個數字到局部變量表

  操作數棧被清空,局部變量表存儲500

JVM - 位元組碼執行

執行sipush 200

  把200 壓入操作數棧

istore_2

  把200放入局部變量表,操作數棧被清空

JVM - 位元組碼執行

執行bipush 50

  把50 壓入操作數棧

istore_3

  把50放入局部變量表,操作數棧被清空

JVM - 位元組碼執行

執行iload_1

  把局部變量表的第一個位置的整數資料壓入到操作數棧

執行iload_2

  把局部變量表的第二個位置的整數資料壓入到操作數棧

JVM - 位元組碼執行

執行iadd

  将兩個數從操作數棧中彈出,做相加操作,再将相加的值壓入操作數棧

執行iload_3

  将局部變量表第三個位置的整數資料壓入到操作數棧

JVM - 位元組碼執行

執行idiv

  從操作數棧中彈出兩個數,做除法,将結果壓入操作數棧

執行ireturn

  方法體的傳回,傳回值為操作數棧的棧頂的元素

JVM - 位元組碼執行
JVM - 位元組碼執行

位元組碼指令為一個byte整數

左半部分:注解符,幫助了解

右半部分:在程式中真正的表示方式

_nop                  =   , // 0x00
_aconst_null          =   , // 0x01
_iconst_0             =   , // 0x03
_iconst_1             =   , // 0x04
_dconst_1             =  , // 0x0f
_bipush               =  , // 0x10
_iload_0              =  , // 0x1a
_iload_1              =  , // 0x1b
_aload_0              =  , // 0x2a
_istore               =  , // 0x36
_pop                  =  , // 0x57
_imul                 = , // 0x68
_idiv                 = , // 0x6c
           

void serAge(int) 方法的位元組碼:

2A 1B B5 00 20 B1

2A _aload_0

    無參

    将局部變量slot0 作為引用 壓入操作數棧

1B _iload_1

    無參

    将局部變量slot1 作為整數 壓入操作數棧

B5 _putfield

    設定對象中字段的值

    參數為        2bytes (00 20) (指明了字段)

        指向常量池        的引用

        Constant_Fieldref

        此處為User.age

    彈出棧中2個對象:objectref, value

    将棧中的value賦給objectref的給定字段

B1 _return

常量入棧

  aconst_null null對象入棧

  iconst_m1 int常量-1入棧

  iconst_0 int常量0入棧

  iconst_5 int常量5入棧

  lconst_1 long常量1入棧

  fconst_1 float 1.0入棧

  dconst_1 double 1.0 入棧

  bipush 8位帶符号整數入棧

  sipush 16位帶符号整數入棧

  ldc 常量池中的項入棧

局部變量壓棧

xload(x為i l f d a)

        分别表示int,long,float,double,object ref

xload_n(x為i l f d a,n為0 1 2 3)

        表示第幾個變量入棧

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

出棧裝載入局部變量

xstore(x為i l f d a)

   出棧,存入局部變量

xstore_n(x為i l f d a, n 0 1 2 3)

  出棧,将值存入第n個局部變量

xastore(x為i l f d a b c s)

  将值存入數組中

    iastore

       執行前,棧:…,arrayref, index, value

       執行後,棧(被清空):…

       将value存入arrayref[index]

通用棧操作(無類型)

  nop 什麼都不做

  pop

    彈出棧頂1個字長

  dup

    複制棧頂1個字長,複制内容壓入棧

類型轉換

  • i2l
  • i2f
  • l2i
  • l2f
  • l2d
  • f2i
  • f2d
  • d2i
  • d2l
  • d2f
  • i2b
  • i2c
  • i2s

例子:i2l

将int轉為long

執行前,棧:…, value

執行後,棧:…,result.word1,result.word2

彈出int,擴充為long,并入棧

整數運算

  • iadd
  • ladd
  • isub
  • lsub
  • idiv
  • ldiv
  • imul
  • lmul
  • iinc(++)

浮點運算

  • fadd
  • dadd
  • fsub
  • dsub
  • fdiv
  • ddiv
  • fmul
  • dmul

對象操作指令

  new 生成對象

  getfield 從對象中拿出給定的值

  putfield 從對象中設定給定的值

  getstatic 針對靜态的字段拿值

  putstatic 針對靜态的字段設定值

條件控制

  ifeq 如果為0,則跳轉

  ifne 如果不為0,則跳轉

  iflt 如果小于0 ,則跳轉

  ifge 如果大于0,則跳轉

  if_icmpeq 如果兩個int相同,則跳轉

例子:ifeq

參數 byte1,byte2

value出棧 ,如果棧頂value為0則跳轉到(byte1<<8)|byte2

執行前,棧:…,value

執行後,棧:…

方法調用

  invokevirtual: 對一個普通執行個體的類方法進行調用(動态綁定)

  invokespecial: 對父類的方法進行調用(靜态綁定)

  invokestatic: 對靜态的方法進行調用

  invokeinterface: 對接口方法進行調用

  xreturn(x為 i l f d a 或為空): 方法的傳回

使用ASM生成Java位元組碼

ASM

  jva位元組碼操作架構

  可以用于修改現有類或者動态産生新類

  使用者:

    AspectJ

    Clojure

    Eclipse

    spring

    cglib

       hibernate

代碼實作例子:

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, );  //this 入棧
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");  
mw.visitInsn(RETURN);  
mw.visitMaxs(, );  
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(,);  
mw.visitEnd();  
byte[] code = cw.toByteArray();  
AsmHelloWorld loader = new AsmHelloWorld();  
Class exampleClass = loader  
    .defineClass("Example", code, , code.length);  
exampleClass.getMethods()[].invoke(null, new Object[] { null }); 
           

模拟實作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(); 
     } 
}
           

輸出結果

SecurityChecker.checkSecurity ...
operation....
           

JIT及其相關參數

位元組碼執行性能較差,是以可以對于熱點代碼編譯成機器碼再執行,在運作時的編譯,叫做JIT Just-In-Time

JIT的基本思想是,将熱點代碼,就是執行比較頻繁的代碼,編譯成機器碼

JIT

當虛拟機發現某個方法或代碼塊運作特别頻繁時,就會把這些代碼認定為”Hot Spot Code”(熱點代碼),為了提高熱點代碼的執行效率,在運作時,虛拟機會把這些代碼編譯成與本地平台相關的機器碼

JVM - 位元組碼執行

-XX:CompileThreshold=1000, 調用多少次的門檻值設定

-XX:+PrintCompilation: 打出方法編譯的資訊

public class JITTest {

    public static void met(){
        int a=,b=;
        b=a+b;
    }

    public static void main(String[] args) {
        for(int i=;i<;i++){
            met();
        }
    }
}
           

運作參數

運作輸出:

java.lang.String::hashCode ( bytes)
                 java.lang.String::equals ( bytes)
                 java.lang.String::indexOf ( bytes)
                 java.lang.String::charAt ( bytes)
                 java.lang.String::length ( bytes)
                 java.lang.String::lastIndexOf ( bytes)
                 java.lang.String::toLowerCase ( bytes)
                 geym.jvm.ch2.jit.JITTest::met ( bytes)
           

可見,met方法被編譯了

JIT的其餘三個參數

-Xint

  解釋執行(不做編譯,執行性能比較差)

-Xcomp

  全部編譯執行(不管方法被調用的次數,是以方法都會被編譯成機器碼,在啟動時性能會非常糟糕,在編異完成後,性能就會好很多)

-Xmixed

  預設,混合(解釋執行和編譯執行,取決于方法被調用的次數)

繼續閱讀