JVM概述與字節碼

JVM概述與字節碼


一、JAVA程序運行機制

1. 高級語言的運行機制

  • 編譯型:c 、C++、FORTRAN、Pascal等
  • 解釋型:Ruby、Python
  • 編譯+解釋型:Visual
    Basic,半編譯型語言,首先被編譯為P-代碼,並將解釋引擎封裝在可執行性程序內,當運行程序時,P-代碼會被解析成真正的二進制代碼。Visual
    Basic編譯的EXE文件中,既有程序的啟動代碼,也有鏈接解釋程序的代碼,而這部分代碼負責啟動Visual
    Basic解釋程序,再對VisualBasic代碼進行解釋並執行。
  • 虛擬機:Java/Groovy/Scala,.Net,

2. Java程序的運行機制和JVM

Java語言比較特殊,程序運行的窗口是JVM虛擬機(Java Virutal Machine)。

JVM概述與字節碼


JVM是可運行Java字節文件的虛擬計算機。所有平臺上的JVM向編譯器提供相同的編程接口,而編譯器只需要面向虛擬機,生成虛擬機能理解的字節碼(可以理解成JVM使用的機器碼),然後由虛擬機來解釋執行。

在一些虛擬機的實現中,還會使用just-in-time的編譯器將虛擬機代碼進行進一步的編譯,轉換成特定系統的機器碼執行,從而提高執行效率。

已故的Sun公司制定的Java虛擬機規範在技術上規定了JVM的統一標準,具體定義了JVM的如下細節:

  • 指令集
  • 寄存器
  • 類文件的格式
  • 垃圾回收堆
  • 存儲區

可以看出虛擬機實現了一個比較完整的操作系統內核,

3. JVM指令集

JVM的指令集目前的官網文檔在:
https://docs.oracle.com/javase/specs/jvms/se12/html/jvms-6.html
JAVA 指令集目前有2百條左右指令(8bit,總數不會超過255個),如果按物理的CPU指令劃分,也算是複雜指令集了。


JVM概述與字節碼


JVM概述與字節碼


JVM概述與字節碼


JVM概述與字節碼


JVM概述與字節碼


JVM概述與字節碼


JVM概述與字節碼


JVM概述與字節碼


JVM概述與字節碼


JVM概述與字節碼


4. 寄存器

JVM的指令集是基於棧,而不是基於寄存器的,其用到的寄存器很少。

  • JAVA虛擬機中的寄存器是PC(程序運行計數器)
  • 其它寄存器利用物理CPU的寄存器:堆棧指針寄存器、框架寄存器、變量寄存器。

JVM讓物理CPU直接執行Java程序所對應的目標機器碼,而不需要實現一個虛擬的CPU。讓CPU執行java的代碼時,需要對CPU場景進行上下文切換,這部分需要佔用比較大的CPU資源。

JVM調用函數的時候,Java函數的代碼並沒有被存放到代碼段中,而是被放在了一個code緩存中,每一個Java函數的代碼塊在這個code緩存中都會有一個索引位置,最終JVM會跳轉到這個索引位置處執行Java函數調用。同時Java的函數一定是封裝在類中的,因此JVM在執行函數調用時,還需要通過類尋址等一系列運算最終才能定位這個入口。

二、字節碼文件格式

1. java編譯器流程:

JVM概述與字節碼

2. 查看字節碼示例

寫一段簡單的Java程序:

<code>package com.xundh;

public class Main {

    public static void main(String[] args) {
        int c=add(1,2);
        System.out.println("Hello World");
        System.out.println("c="+c);
    }
    public static int add(int a,int b){
        return a+b;
    }
}
/<code>

執行下面命令:

<code>javac Main.java
javap -verbose Main.class/<code>

分析字節碼文件後輸出如下內容:

<code>D:\Documents\Downloads\learn-java\src\com\xundh>javap -verbose Main.class
Classfile /D:/Documents/Downloads/learn-java/src/com/xundh/Main.class
  Last modified 2020-9-15; size 947 bytes
  MD5 checksum 063ed126168378bb07d6610d353ad144
  Compiled from "Main.java"
public class com.xundh.Main
  minor version: 0                             // 子版本
  major version: 55                           // 主版本
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#19         // java/lang/Object."":()V
   #2 = Methodref          #7.#20         // com/xundh/Main.add:(II)I                  add方法
   #3 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = String             #23            // Hello World
   #5 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = InvokeDynamic      #0:#29         // #0:makeConcatWithConstants:(I)Ljava/lang/String;
   #7 = Class              #30            // com/xundh/Main
   #8 = Class              #31            // java/lang/Object
   #9 = Utf8               
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               main                     // 方法 
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               add                       // 方法
  #16 = Utf8               (II)I
  #17 = Utf8               SourceFile            // 源文件名
  #18 = Utf8               Main.java
  #19 = NameAndType        #9:#10         // "":()V
  #20 = NameAndType        #15:#16        // add:(II)I
  #21 = Class              #32            // java/lang/System
  #22 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello World
  #24 = Class              #35            // java/io/PrintStream
  #25 = NameAndType        #36:#37        // println:(Ljava/lang/String;)V
  #26 = Utf8               BootstrapMethods
  #27 = MethodHandle       #6:#38         // invokestatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lan
g/Object;)Ljava/lang/invoke/CallSite;
  #28 = String             #39            // c=╔
  #29 = NameAndType        #40:#41        // makeConcatWithConstants:(I)Ljava/lang/String;
  #30 = Utf8               com/xundh/Main
  #31 = Utf8               java/lang/Object
  #32 = Utf8               java/lang/System
  #33 = Utf8               out
  #34 = Utf8               Ljava/io/PrintStream;
  #35 = Utf8               java/io/PrintStream
  #36 = Utf8               println
  #37 = Utf8               (Ljava/lang/String;)V
  #38 = Methodref          #42.#43        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Lja
va/lang/invoke/CallSite;
  #39 = Utf8               c=╔
  #40 = Utf8               makeConcatWithConstants
  #41 = Utf8               (I)Ljava/lang/String;
  #42 = Class              #44            // java/lang/invoke/StringConcatFactory
  #43 = NameAndType        #40:#48        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #44 = Utf8               java/lang/invoke/StringConcatFactory
  #45 = Class              #50            // java/lang/invoke/MethodHandles$Lookup
  #46 = Utf8               Lookup
  #47 = Utf8               InnerClasses
  #48 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #49 = Class              #51            // java/lang/invoke/MethodHandles
  #50 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #51 = Utf8               java/lang/invoke/MethodHandles
{
  public com.xundh.Main();
    descriptor: ()V
    flags: ACC_PUBLIC                  // 類的訪問權限
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_1                                 // 前面是偏移地址,後面是操作指令 
         1: iconst_2
         2: invokestatic  #2                  // Method add:(II)I   調用靜態函數
         5: istore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: ldc           #4                  // String Hello World
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: iload_1
        18: invokedynamic #6,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
        23: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        26: return
      LineNumberTable:                 // 源碼與字節碼對應關係 
        line 6: 0
        line 7: 6
        line 8: 14
        line 9: 26

  public static int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 11: 0
}
SourceFile: "Main.java"
InnerClasses:
     public static final #46= #45 of #49; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #27 invokestatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite
;
    Method arguments:
      #28 c=╔

/<code> 

對應的十六進制文件解析:

JVM概述與字節碼

JVM概述與字節碼


部分字段解釋:

  • u4 : MagicNumber 前4個字節 固定值 0xCAFEBABE
  • 2個u2 : Version 版本號4個字節,跟在MagicNumber後面
  • 1個u2 : Constant_pool 常量數量
  • 接下來是常量池
  • Fields+attributes 字段信息
  • Methods + attributes 方法信息


3. 常量池

常量池的數據通常有兩種類型:

  • 字面量,如字符串、final修飾的常量
  • 符號引用: 類/接口的全限定名、方法的名稱和描述、字段的名稱和描述等
JVM概述與字節碼

三、反彙編字節碼

下面的操作需要 hsdis的支持,下載地址:
https://sourceforge.net/projects/fcml/files/fcml-1.1.3/
把dll放到 \jdk1.8.0_31\jre\bin\server 目錄下。
在IDEA中設置 VM options:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
編譯就會顯示反彙編的代碼。

JVM概述與字節碼


反彙編的示例:

JVM概述與字節碼


可以看到字節碼到彙編以後的代碼數量是暴增的。

四、關於動態修改字節碼

由於java的字節碼文件是有明確規格的,所以可以通過對字節碼的修改來動態的修改類,這就是AOP技術的核心。但直接對字節碼進行修改非常困難,需要對字節碼非常熟悉。一些第三方的庫可以簡化這種修改,如:

  • ASM 基於 Java 字節碼層面的代碼分析和修改工具,可以直接產生二進制class文件,也可以在類被加載到JVM前動態改變類行為;
  • javassist 開源的分析、編輯和創建Java字節碼的類庫,是jboss的一個子項目
  • cglib 一個高性能的代碼生成包,實現JDK的動態代理,被應用在Spring AOP、dynaop、Hibernate等框架中。
  • BCEL Apache Byte Code Engineering Library,實現對字節碼文件的功能擴充。


分享到:


相關文章: