本文已收錄 【修煉內功】躍遷之路
學習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文件,目的是爲了瞭解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"語言的時候就已經肯定下來了
緊接着魔術的四個字節(接下來再也不對照二進制進行查看,而是直接查看javap幫咱們解析出來的結果,見文章末尾)存儲的是Class文件的版本號,前兩個字節爲次版本號(minor version),後兩個字節爲主版本號(major version)
Java版本號從45開始,高版本的JDK能夠向下兼容低版本的Class文件,但沒法向上兼容高版本,即便文件格式並未發生變化
緊接着主次版本號以後的是常量池入口
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic Reference)
字面量:如文本字符串、被聲明爲final的常量值等
符號引用:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述(Descriptor)、方法的名稱和描述符
Java代碼在進行編譯時,並不像C或C++那樣有"鏈接"這一步驟,而是在虛擬機加載Class文件時進行動態鏈接,Class文件中不會保存各方法和字段的內存佈局,在虛擬機運行時,須要從常量池中得到對應的符號引用,再在類建立或運行時解析並翻譯到具體的內存地址中,才能被虛擬機使用
訪問標誌用於識別一些類或接口層次的訪問信息
訪問標誌用於識別這個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語句)
字段表(field_info)用於描述類或者接口中聲明的變量
字段(field)包括了類級變量(如static)及實例級變量,但不包括在方法內部聲明的變量
字段包含的信息有:做用域(public、private、protected)、類級仍是實例級(static)、可變性(final)、併發可見性(volatile)、能否序列化(transient)、數據類型、字段名等
這裏簡單解釋一下描述符(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文件、字段表、方法表中均可以攜帶本身的屬性表集合,用於描述某些場景專有的信息,Java虛擬機規範中預約義了9種虛擬機實現應當能識別的屬性
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節碼指令 |
ConstantValue | 字段表 | final關鍵自定義的常量值 |
Deprecated | 類、方法表、字段表 | 被聲明爲deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
InnerClasses | 類文件 | 內部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節碼指令的對應關係 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
SourceFile | 類文件 | 原文件名稱 |
Synthetic | 類、方法表、字段表 | 標識方法或字段爲編譯器自動生成的 |
關於屬性表,會在以後的文章中穿插介紹
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虛擬機