Java 實現一次編譯處處運行的基礎,來源於 Java 虛擬機屏蔽了操做系統的底層細節。使用 class 文件存儲編譯後的源程序,使得 Java 程序的編譯與操做系統解耦。正是由於 Java class 文件的設計與 Java 語言解耦,分別發佈了 Java語言規範和 Java 虛擬機規範,使得其餘語言如Scala、Groovy、JRuby、JPython 等基於Java 虛擬機的語言按照 class 文件格式要求生成的class 文件也能在虛擬機上運行。java
class 文件格式
class 文件採用以下的結構存儲二進制內容。其中 u二、u4 分別表示佔用 二、4 個字節。post
{this
u4 magic; //魔數,固定爲0xCAFEBABE操作系統
u2 minor_version; //次版本號設計
u2 major_version; //主版本號blog
u2 constant_pool_count; //常量池計數器索引
cp_info constant_pool[constant_pool_count-1]; //常量池接口
u2 access_flags; //訪問標誌,聲明權限作用域
u2 this_class; //類索引字符串
u2 super_class; //父類索引
u2 interfaces_count; //接口個數
u2 interfaces[interfaces_count]; //接口列表
u2 fields_count; //字段個數
field_info fields[fields_count];//字段列表
u2 methods_count; //方法個數
method_info methods[methods_count]; //方法列表
u2 attributes_count; //屬性個數
attribute_info attributes[attributes_count]; //屬性列表
}
class 文件內容解讀
常量池:
存儲 class 文件用到的全部的字符串常量、類名、接口名、字段名以及其餘常量。class 文件的其餘項目每每會引用常量池中的常量,所以常量池容量計數從1開始,0 用於表示其餘項目不引用常量池。在常量池中主要存儲了字面量和符號引用兩大類常量,字面量主要是字符串、final 類型常量值等,符號引用則包括類和接口的全限定名、字段的名稱和描述法以及方法的名稱和描述符。在前文《 Java 虛擬機類加載機制》中提到的符號引用轉換爲直接引用中的符號引用就是常量池中的符號引用。
訪問標誌:
類或接口的訪問權限信息,包括 public、final、super、interface、abstract、annotation、enum 幾種屬性,以及使用 synthetic 表示非 Java 源碼生成的代碼。
類索引:
this_class 存儲常量池中的一個索引,索引處的常量表示 class 文件定義的類或接口。若是這是一個類,super_class 爲 0 或存儲常量池中的一個索引,索引處的常量表示父類;若是這是一個接口,super_class 存儲常量池中的一個索引,索引處的常量必定是 java.lang.Object。經過 this_class 能夠肯定當前類的全限定名,經過 super_class 能夠肯定父類的全限定名。
接口列表:
若是這是一個類,存儲該類實現的接口列表,按照 implements 後的接口順序存儲;若是這是一個接口,存儲該接口的全部父接口列表,按照 extends 後的接口順序存儲。
字段列表:
存儲類或接口聲明的變量,包括類變量和實例變量。描述了每一個變量的信息,包括做用域、static、final、volatile、transient、類型、名稱等。其中字段的名稱、類型須要引用常量池中的常量來描述。
方法列表:
存儲類或接口聲明的方法,包括類方法和實例方法。描述了每一個方法的信息,包括訪問標誌、名稱索引、描述符索引、屬性表集合等。這裏僅僅存儲了方法的信息,方法的實現代碼編譯成字節碼後存儲在屬性表集合中的 「 Code 」 屬性裏面。
屬性列表:虛擬機規範定義了大量的屬性,class 文件、字段列表、方法列表均可以使用屬性描述專有信息。而屬性的名稱須要引用常量池的常量來表示。方法體中的代碼經編譯後就存放在名爲 Code 的屬性中。
總結
Java 源程序編譯後生成 class 文件而不是二進制可執行文件,經過 Java 虛擬機來解析並執行 class 文件中的程序,實現了「一次編譯,處處運行」。在 class 文件中,存儲了類或接口的基本信息,如版本號、類名、接口列表、字段列表、方法列表等。