【修煉內功】[JVM] 類文件結構

本文已收錄 【修煉內功】躍遷之路

類文件結構

學習C語言的時候,須要在不一樣的目標操做系統上(或者使用交叉編譯環境),(使用正確的CPU指令集)編譯成對應操做系統可運行的執行文件,才能夠在相應的系統上運行,若是使用操做系統差別性的庫或者接口,還須要針對不一樣的系統作不一樣的處理(宏)java

Java的出現也正是爲了解決"平臺無關性","Write Once, Run Anywhere"的口號也充分表達了軟件開發人員對衝破平臺接線的渴求segmentfault

"與平臺無關"的最終實現仍是要在操做系統的應用層上,這即是JVM的存在,不一樣的平臺有不一樣的JVM,而全部的JVM均可以載入與平臺無關的字節碼,從而實現程序的"一次編寫,處處運行"數組

JVM並不是只爲Java設計,而字節碼也並不是只有Java才能夠編譯獲得,早在Java發展之初,設計者便將Java規範拆分爲Java語言規範Java虛擬機規範,同時也承諾,對JVM作適當的擴展,以便更好地支持其餘語言運行於JVM之上,而這一切的基礎即是Class文件(字節碼文件),Class文件中存放了JVM能夠理解運行的字節碼命令併發

In the future, we will consider bounded extensions to th Java virtual machine to provide better support for other languages

JVM並不關心Class的來源是何種語言,在JVM發展到1.7~1.8的時候,設計者經過JSR-292基本兌現了以上承諾app

class_compile

本篇不會去詳細地介紹如何去解析Class文件,目的是爲了瞭解Class文件的結構,Class文件中都包含哪些內容ide

Class文件能夠由JVM加載並執行,其中記錄了類信息、變量信息、方法信息、字節碼指令等等,雖然JVM加載Class以後(在JIT以前)進行的是解釋執行,但Class文件並非文本文件,而是被嚴格定義的二進制流文件佈局

接下來,均會以這段代碼爲示例進行分析學習

import java.io.Serializable;

public class ClassStruct implements Serializable {
    private static final String HELLO = "hello";
    private String name;

    public ClassStruct(String name) {
        this.name = name;
    }

    public void print() {
        System.out.println(HELLO + ": " + name);
    }

    public static void main(String[] args) {
        ClassStruct classStruct = new ClassStruct("ManerFan");
        classStruct.print();
    }
}

使用$ javac ClassStruct.java進行編譯,編譯後的文件可使用$ javap -p -v ClassStruct查看Class文件的內容(見文章末尾)ui

魔數

不少文件存儲都會使用魔數來進行身份識別,好比圖片文件,即便將圖片文件改成不正確的後綴,絕大多數圖片預覽器也會正確解析this

一樣Class文件也不例外,使用二進制模式打開Class文件,會發現全部Class文件的前四個字節均爲OxCAFEBABE,這個魔術在Java還被稱爲"Oak"語言的時候就已經肯定下來了

magic_number

版本號

緊接着魔術的四個字節(接下來再也不對照二進制進行查看,而是直接查看javap幫咱們解析出來的結果,見文章末尾)存儲的是Class文件的版本號,前兩個字節爲次版本號(minor version),後兩個字節爲主版本號(major version)

class_1

Java版本號從45開始,高版本的JDK能夠向下兼容低版本的Class文件,但沒法向上兼容高版本,即便文件格式並未發生變化

常量池

緊接着主次版本號以後的是常量池入口

常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic Reference)

字面量:如文本字符串、被聲明爲final的常量值等
符號引用:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述(Descriptor)、方法的名稱和描述符

class_2

Java代碼在進行編譯時,並不像C或C++那樣有"鏈接"這一步驟,而是在虛擬機加載Class文件時進行動態鏈接,Class文件中不會保存各方法和字段的內存佈局,在虛擬機運行時,須要從常量池中得到對應的符號引用,再在類建立或運行時解析並翻譯到具體的內存地址中,才能被虛擬機使用

訪問標誌

訪問標誌用於識別一些類或接口層次的訪問信息

class_3

訪問標誌用於識別這個Class是類仍是接口;是否認義爲public;是否爲abstract類型;是否聲明爲final;等等,具體標誌含義以下

標誌 名稱
ACC_PUBLIC 是否爲public類型
ACC_FINAL 是否被聲明爲final
ACC_SUPER 是否容許使用invokespecial字節碼指令
ACC_INTERFACE 是否爲接口
ACC_ABSTRACT 是否爲abstract
ACC_SYNTHETIC 標識這個類並不是由用戶代碼生成
ACC_ANNOTATION 標識這是一個註解
ACC_ENUM 標識這是一個枚舉

類索引、父類索引、接口索引集合

Class文件中由類索引(this_class)、父類索引(super_class)及接口索引集合(interfaces)三項數據肯定這個類的繼承關係

父類索引只有一個(對應extends語句),而接口索引則是一個集合(對應implements語句)

class_4

字段表集合

字段表(field_info)用於描述類或者接口中聲明的變量

字段(field)包括了類級變量(如static)及實例級變量,但不包括在方法內部聲明的變量

字段包含的信息有:做用域(public、private、protected)、類級仍是實例級(static)、可變性(final)、併發可見性(volatile)、能否序列化(transient)、數據類型、字段名等

class_5

描述符

這裏簡單解釋一下描述符(descriptor)

描述符用來描述字段數據類型、方法參數列表和返回值,根據描述符規則,基本數據類型及表明無返回值的void類型都用一個大寫字符表示,對象類型則用字符L加對象全限定名來表示

標識字符 含義
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 對象類型,如 Ljava/lang/Object;

對於數組,每個維度使用一個前置的[來描述,如java.lang.String[][]將被記錄爲[[java/lang/String;int[]將被記錄爲[I

描述方法時,按照先參數列表,後返回值的順序描述,參數列表放在()內,如void inc()描述符爲()V,方法java.lang.String toString()描述符爲()Ljava/lang/String;,方法int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex的描述符爲([CII[CIII)I

方法表集合

Class文件中對方法的描述與對字段的描述幾乎採用了徹底一致的方式,方法表的結構依次包括了訪問標誌(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes),可是方法內部的代碼並不在方法表中,而是通過編譯器編譯成字節碼指令後,存放在屬性表集合中一個名爲"Code"的屬性中

class_6

屬性表集合

在Class文件、字段表、方法表中均可以攜帶本身的屬性表集合,用於描述某些場景專有的信息,Java虛擬機規範中預約義了9種虛擬機實現應當能識別的屬性

屬性名稱 使用位置 含義
Code 方法表 Java代碼編譯成的字節碼指令
ConstantValue 字段表 final關鍵自定義的常量值
Deprecated 類、方法表、字段表 被聲明爲deprecated的方法和字段
Exceptions 方法表 方法拋出的異常
InnerClasses 類文件 內部類列表
LineNumberTable Code屬性 Java源碼的行號與字節碼指令的對應關係
LocalVariableTable Code屬性 方法的局部變量描述
SourceFile 類文件 原文件名稱
Synthetic 類、方法表、字段表 標識方法或字段爲編譯器自動生成的

關於屬性表,會在以後的文章中穿插介紹

附:Class文件

Classfile ~/articles/【修煉內功】躍遷之路/JVM/[JVM] 類文件結構/src/ClassStruct.class
  Last modified 2019-6-2; size 829 bytes
  MD5 checksum 9f7454acd0455837a33ff8e03edffdb3
  Compiled from "ClassStruct.java"
public class ClassStruct implements java.io.Serializable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#31        // java/lang/Object."<init>":()V
   #2 = Fieldref           #6.#32         // ClassStruct.name:Ljava/lang/String;
   #3 = Fieldref           #33.#34        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Class              #35            // java/lang/StringBuilder
   #5 = Methodref          #4.#31         // java/lang/StringBuilder."<init>":()V
   #6 = Class              #36            // ClassStruct
   #7 = String             #37            // hello:
   #8 = Methodref          #4.#38         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #9 = Methodref          #4.#39         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #10 = Methodref          #40.#41        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = String             #42            // ManerFan
  #12 = Methodref          #6.#43         // ClassStruct."<init>":(Ljava/lang/String;)V
  #13 = Methodref          #6.#44         // ClassStruct.print:()V
  #14 = Class              #45            // java/lang/Object
  #15 = Class              #46            // java/io/Serializable
  #16 = Utf8               HELLO
  #17 = Utf8               Ljava/lang/String;
  #18 = Utf8               ConstantValue
  #19 = String             #47            // hello
  #20 = Utf8               name
  #21 = Utf8               <init>
  #22 = Utf8               (Ljava/lang/String;)V
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               print
  #26 = Utf8               ()V
  #27 = Utf8               main
  #28 = Utf8               ([Ljava/lang/String;)V
  #29 = Utf8               SourceFile
  #30 = Utf8               ClassStruct.java
  #31 = NameAndType        #21:#26        // "<init>":()V
  #32 = NameAndType        #20:#17        // name:Ljava/lang/String;
  #33 = Class              #48            // java/lang/System
  #34 = NameAndType        #49:#50        // out:Ljava/io/PrintStream;
  #35 = Utf8               java/lang/StringBuilder
  #36 = Utf8               ClassStruct
  #37 = Utf8               hello:
  #38 = NameAndType        #51:#52        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #39 = NameAndType        #53:#54        // toString:()Ljava/lang/String;
  #40 = Class              #55            // java/io/PrintStream
  #41 = NameAndType        #56:#22        // println:(Ljava/lang/String;)V
  #42 = Utf8               ManerFan
  #43 = NameAndType        #21:#22        // "<init>":(Ljava/lang/String;)V
  #44 = NameAndType        #25:#26        // print:()V
  #45 = Utf8               java/lang/Object
  #46 = Utf8               java/io/Serializable
  #47 = Utf8               hello
  #48 = Utf8               java/lang/System
  #49 = Utf8               out
  #50 = Utf8               Ljava/io/PrintStream;
  #51 = Utf8               append
  #52 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #53 = Utf8               toString
  #54 = Utf8               ()Ljava/lang/String;
  #55 = Utf8               java/io/PrintStream
  #56 = Utf8               println
{
  private static final java.lang.String HELLO;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
    ConstantValue: String hello

  private java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE

  public ClassStruct(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: aload_1
         6: putfield      #2                  // Field name:Ljava/lang/String;
         9: return
      LineNumberTable:
        line 7: 0
        line 8: 4
        line 9: 9

  public void print();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #4                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        10: ldc           #7                  // String hello:
        12: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: getfield      #2                  // Field name:Ljava/lang/String;
        19: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        22: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        25: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        28: return
      LineNumberTable:
        line 12: 0
        line 13: 28

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #6                  // class ClassStruct
         3: dup
         4: ldc           #11                 // String ManerFan
         6: invokespecial #12                 // Method "<init>":(Ljava/lang/String;)V
         9: astore_1
        10: aload_1
        11: invokevirtual #13                 // Method print:()V
        14: return
      LineNumberTable:
        line 16: 0
        line 17: 10
        line 18: 14
}
SourceFile: "ClassStruct.java"

參考:
深刻理解Java虛擬機

訂閱號

相關文章
相關標籤/搜索