前面衆多文章有關講解的都是些JVM的內存與垃圾回收器相關信息,那麼對於本篇開始咱們將把目光轉移到Class文件與加載器身上去,去看看字節碼文件裏到底有些什麼信息?是怎麼加載到咱們內存裏?html
================================前端
當Java源代碼成功編譯成字節碼後,若是想在不一樣的平臺上面運行,則無須再次編譯java
這個優點再也不那麼吸引人了。Python、PHP、Per一、Ruby、Lisp等有強大的解釋器面試
跨平臺彷佛已經快成爲一門語言必選的特性後端
Java虛擬機不和包括Java 在內的任何語言綁定,它只與「Class 文件」這種特定的二進制文件格式所關聯。數組
不管使用何種語言進行軟件開發,只要能將源文件編譯爲正確的Class文件,那麼這種語言就能夠在Java虛擬機上執行。能夠說,統一而強大的Class文件結構,就是Java虛擬機的基石、橋樑。網絡
可訪問官方入口查看詳細的規範:訪問地址oracle
遵照Java虛擬機規範,也就是說全部的JVM環境都是同樣的,這樣一來字節碼文件能夠在各類Jw上運行。框架
================================eclipse
前端編譯器的主要任務就是負責將符合Java語法規範的Java代碼轉換爲符合JVM規範的字節碼文件
javac是一種可以將Java源碼編譯爲字節碼的前端編譯器
Javac編譯器在將Java源碼編譯爲一個有效的字節碼文件過程當中經歷了4個步驟分別是
HotSpot VN並無強制要求前端編譯器只能使用javac來編譯字節碼
,其實只要編譯結果符合JVW規範均可以被JVM所識別便可
在Java的前端編譯器領域除了javac以外
,還有一種被你們常常用到的前端編譯器,那就是內置在Eclipse中的ECJ(EclipseCompiler for Java)編譯器
和Javac的全量式編譯不一樣,EC是一種增量式編譯器
在Eclipse中,當開發人員編寫完代碼後,使用「Ctrl+S」快捷鍵時,ECJ編譯器所採起的編譯方案是把未編譯部分的源碼逐行進行編譯,而非每次都全量編譯
所以ECJ的編譯效率會比javac更加迅速和高效,固然編譯質量和javac相比大體仍是同樣的
EC不只是Eclipse的默認內置前端編譯器,在Tomcat中一樣也是使用ECJ編譯器來編譯jsp文件
因爲ECJ編譯器是採用GPLv2的開源協議進行源代碼公開,因此你們能夠登陸eclipse官網下載ECJ編譯器的源碼進行二次開發。
默認狀況下,IntelliJ IDEA使用javac編譯器。(還能夠本身設置爲Aspect]編譯器ajc)
前端編譯器並不會直接涉及編譯優化等方面的技術,而是將這些具體優化細節移交給HotSpot的3IT編譯器負責
================================
先來看看幾個常見的面試題
在面對這些問題的時候,接下來先看下面這個示例代碼是怎麼回事
public class IntegerTesti public static void main( String[] args) { Integer x = 5; int y = 5; System.out.print1n(x == y); Integer i1 = 10; Integer i2 = 10; System.out.print1n(i1 == i2); Integer i3 = 128; Integer i4 = 128; System.out.println(i3 == i4); } }
按照咱們前面的知識,咱們能夠回顧一下看看操做數棧與局部變量表裏是怎麼樣操做的?
接下來咱們能夠在使用一個示例代碼來體會這種過程
public class stringTest { public static void main( string[] args) i string str = new string("hello") + new String("world"); String str1 ="hellowor1d"; system.out.println(str == str1); } }
咱們回顧回顧前面知識點,看看第一行代碼 + 符號作了些什麼事情
接下來咱們能夠在使用一個示例代碼來體會這種過程
class Father{ int x = 10; public Father(){ this.print(); x = 20; } public void print() { system.out.println( "Father.x = " + x); } }
class son extends Father{ int x = 30; public son(){ this.print(); x = 40; } //重寫父類的方法 public void print(){ system.out.print1n( "Son.x = 」 +x); } }
此時咱們用一個Test類調用這裏兩個類,看看將程序運行後會輸出什麼呢?
public class SonTest{ public static void main(String[] args) { Father f = new Son(); system.out.println(f.x); } } //運行結果以下: Son.x = 0; Son.x = 30; 20
這時會有小夥伴好奇了,爲何會是這樣的一個輸出結果呢?咱們先看簡單的是什麼狀況
public class SonTest{ public static void main(String[] args) { Father f = new Father(); system.out.println(f.x); } } //運行結果以下: Father.x = 10 20
對於非靜態的成員變量的賦值過程主要分爲如下幾種:
接下來咱們經過字節碼指令的方式看看是否是與咱們說的步驟是差很少的?
此時咱們回到剛剛Test調用Son類的狀況,進行分析看看
public class SonTest{ public static void main(String[] args) { Father f = new Son(); system.out.println(f.x); } } //運行結果以下: Son.x = 0; Son.x = 30; 20
接下來咱們經過字節碼指令的方式看看Son作了哪些事情
以前咱們分析了Father的構造器方法有哪些,以及作了哪些事情
此時調用Father的構造器時,應該執行print方法,可是Son類重寫了因此調用Son類的方法
================================
源代碼通過編譯器編譯以後便會生成一個字節碼文件,字節碼是一種二進制的類文件,它的內容是jVM的指令
,而不像C、C++經由編譯器直接生成機器碼
。
================================
JAVA虛擬機的指令由一個字節長度的、表明着某種特定操做含義的操做碼(opcode)以及跟隨其後的零至多個表明此操做所需參數的操做數(operand)所構成
。
虛擬機中許多指令並不包含操做數,只有一個操做碼,好比說咱們能夠看看上面示例代碼的
================================
================================
可訪問官方入口查看詳細的內容:訪問地址
================================
任何一個Class文件都對應着惟一一個類或接口的定義信息,但反過來講,Class文件實際上它並不必定以磁盤文件的形式存在(可網絡傳)。Class 本質是一組以8位字節爲基礎單位的二進制流。
================================
Class 的結構不像XML等描述語言,因爲它沒有任何分隔符號。
因此在其中的數據項,不管是字節順序仍是數量,都是被嚴格限定的,哪一個字節表明什麼含義,長度是多少,前後順序如何,都不容許改變。
文件格式採用一種相似於c語言結構體的方式進行數據存儲,這種結構中只有:無符號數和表
描述有層次關係的複合結構的數據
,整個Class 文件本質上就是一張表。因爲表沒有固定長度,因此一般會在其前面加上個數說明================================
Class文件的結構並非一成不變的,隨着Java虛擬機的不斷髮展,老是不可避免地會對Class文件結構作出一些調整,可是其基本結構和框架是很是穩定的。
用四個字節來表示魔數,主要對應識別當前文件是否是一個Class文件的標識
當前字節碼文件是在那個版本下運行的編譯,分爲大版本/小版本(主版本/副版本)
經過兩個字節告訴我常量池多長,下面是該常量池的數組
用於當前標識是否爲一個類仍是接口、權限是什麼?有沒有abstract修飾、final修飾等
用於指明當前類是什麼名、父類是什麼名、當前類實現的接口長度、存放的數組等信息
一個類可有多個字段、因此這裏也要代表長度、以及存放字段的數組信息
當前類定義的方法,也有一個方法表的長度、以及存放方法的數組信息
咱們進行一個類的字節碼的時候,通常方法裏Code這些屬性,就指的這