我所知道JVM虛擬機之Class文件結構一(描述介紹)

前言

前面衆多文章有關講解的都是些JVM的內存與垃圾回收器相關信息,那麼對於本篇開始咱們將把目光轉移到Class文件與加載器身上去,去看看字節碼文件裏到底有些什麼信息?是怎麼加載到咱們內存裏?html

1、Class文件的概述


字節碼文件的跨平臺性

================================前端

Java 語言:跨平臺性(write one run anywhere)

當Java源代碼成功編譯成字節碼後,若是想在不一樣的平臺上面運行,則無須再次編譯java

這個優點再也不那麼吸引人了。Python、PHP、Per一、Ruby、Lisp等有強大的解釋器面試

跨平臺彷佛已經快成爲一門語言必選的特性後端

java 虛擬機:跨語言的平臺

Java虛擬機不和包括Java 在內的任何語言綁定,它只與「Class 文件」這種特定的二進制文件格式所關聯。數組

不管使用何種語言進行軟件開發,只要能將源文件編譯爲正確的Class文件,那麼這種語言就能夠在Java虛擬機上執行。能夠說,統一而強大的Class文件結構,就是Java虛擬機的基石、橋樑。網絡

image.png

JAVA語言和JVM的規範

可訪問官方入口查看詳細的規範:訪問地址oracle

遵照Java虛擬機規範,也就是說全部的JVM環境都是同樣的,這樣一來字節碼文件能夠在各類Jw上運行。框架

Java的前端編譯器

================================eclipse

JAVA源代碼遵循JVM規範可正常運行在JVM中

前端編譯器的主要任務就是負責將符合Java語法規範的Java代碼轉換爲符合JVM規範的字節碼文件

javac是一種可以將Java源碼編譯爲字節碼的前端編譯器

Javac編譯器在將Java源碼編譯爲一個有效的字節碼文件過程當中經歷了4個步驟分別是

  • 詞法解析
  • 語法解析
  • 語義解析
  • 生成字節碼

image.png

前端編譯器VS後端編譯器

image.png

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編譯器負責

透過字節碼指令看代碼細節

================================

先來看看幾個常見的面試題

  • 類文件結構分幾個部分?
  • 字節碼都有哪些?Integer x = 5,int y = 5 比較 x == y都通過哪些步驟

在面對這些問題的時候,接下來先看下面這個示例代碼是怎麼回事

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);
    }
}

按照咱們前面的知識,咱們能夠回顧一下看看操做數棧與局部變量表裏是怎麼樣操做的?

image.png

image.png

image.png

image.png

image.png

image.png

接下來咱們能夠在使用一個示例代碼來體會這種過程

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);    
    }
}

咱們回顧回顧前面知識點,看看第一行代碼 + 符號作了些什麼事情

image.png

接下來咱們能夠在使用一個示例代碼來體會這種過程

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

對於非靜態的成員變量的賦值過程主要分爲如下幾種:

  • 默認初始化
  • 顯示初始化/代碼塊初始化
  • 構造器中初始化
  • 有對象後,可經過對象.屬性名調用(權限容許狀況下)

接下來咱們經過字節碼指令的方式看看是否是與咱們說的步驟是差很少的?

image.png

image.png

此時咱們回到剛剛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作了哪些事情

image.png

以前咱們分析了Father的構造器方法有哪些,以及作了哪些事情

image.png

此時調用Father的構造器時,應該執行print方法,可是Son類重寫了因此調用Son類的方法

image.png

image.png

image.png

2、虛擬機的基石:CLass文件


字節碼文件裏是什麼?

================================

源代碼通過編譯器編譯以後便會生成一個字節碼文件,字節碼是一種二進制的類文件,它的內容是jVM的指令,而不像C、C++經由編譯器直接生成機器碼

什麼是字節碼指令(byte code)?

================================

JAVA虛擬機的指令由一個字節長度的、表明着某種特定操做含義的操做碼(opcode)以及跟隨其後的零至多個表明此操做所需參數的操做數(operand)所構成

虛擬機中許多指令並不包含操做數,只有一個操做碼,好比說咱們能夠看看上面示例代碼的

image.png

如何解讀供虛擬機解釋執行的二進制字節碼

================================

方式一:採用notepad++,安裝HEX-Edirot插件或者Binary Viewer插件

image.png

方式二:IDEA插件: jclasslib 或jclasslib bytecode viewer客戶端工具

image.png

方式三:使用Javap指令:jdk自帶反解析工具

image.png

image.png

3、Class文件的結構


官方文檔位置

================================

可訪問官方入口查看詳細的內容:訪問地址

Class 類的本質

================================

任何一個Class文件都對應着惟一一個類或接口的定義信息,但反過來講,Class文件實際上它並不必定以磁盤文件的形式存在(可網絡傳)。Class 本質是一組以8位字節爲基礎單位的二進制流。

Class 文件格式

================================

Class 的結構不像XML等描述語言,因爲它沒有任何分隔符號。

因此在其中的數據項,不管是字節順序仍是數量,都是被嚴格限定的,哪一個字節表明什麼含義,長度是多少,前後順序如何,都不容許改變。

文件格式採用一種相似於c語言結構體的方式進行數據存儲,這種結構中只有:無符號數和表

  • 無符號數屬於基本的數據類型,以u一、u二、u四、u8來分別表明1個字節、2個字節、4個字節和8個字節的無符號數,無符號數能夠用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。
  • 表是由多個無符號數或者其餘表做爲數據項構成的複合數據類型,全部表都習慣性地以「_info」結尾。表用於描述有層次關係的複合結構的數據,整個Class 文件本質上就是一張表。因爲表沒有固定長度,因此一般會在其前面加上個數說明

Class 文件結構

================================

Class文件的結構並非一成不變的,隨着Java虛擬機的不斷髮展,老是不可避免地會對Class文件結構作出一些調整,可是其基本結構和框架是很是穩定的。

Class文件的整體結構以下:
  • 魔數
  • class文件版本常量池
  • 訪問標誌
  • 類索引,父類索引,接口索引集合
  • 字段表集合
  • 方法表集合
  • 屬性表集合

image.png

魔數結構講解:

用四個字節來表示魔數,主要對應識別當前文件是否是一個Class文件的標識

image.png

Class文件版本結構講解:

當前字節碼文件是在那個版本下運行的編譯,分爲大版本/小版本(主版本/副版本)

image.png

常量池結構講解:

經過兩個字節告訴我常量池多長,下面是該常量池的數組

image.png

訪問標識標識講解:

用於當前標識是否爲一個類仍是接口、權限是什麼?有沒有abstract修飾、final修飾等

image.png

類索引、父類索引、接口索引集合講解:

用於指明當前類是什麼名、父類是什麼名、當前類實現的接口長度、存放的數組等信息

image.png

字段表集合講解:

一個類可有多個字段、因此這裏也要代表長度、以及存放字段的數組信息

image.png

方法表集合講解:

當前類定義的方法,也有一個方法表的長度、以及存放方法的數組信息

image.png

屬性表集合講解:

咱們進行一個類的字節碼的時候,通常方法裏Code這些屬性,就指的這

image.png

相關文章
相關標籤/搜索