JAVA虛擬機簡介

Java虛擬機

這裏寫圖片描述

注意:我們這裏說的虛擬機是所謂的高級語言虛擬機, 並不是像Vmware那樣, 完全虛擬一個硬件和操作系統出來。

此外java虛擬機上還可以運行clojure, scala , Jruby, Jptyon等語言

Java虛擬機列表:
- https://www.zhihu.com/question/29265430?sort=created
- https://my.oschina.net/yygh/blog/598187

CPU處理器與操作系統的整體叫平臺,每種CPU都有其特定的指令集,不同的操作系統支持不同CPU的指令集。語言跨平臺是編譯後的文件跨平臺 即.class文件,而不是源程序(.java文件)跨平臺。由於cpu指令集的差異所以Java虛擬機在不同平臺的實現是不一樣的。這樣就不會像彙編語言對平臺的依賴性那麼大。

虛擬機並不是Java的專利

這裏寫圖片描述

Ruby, PHP, Python都有自己的虛擬機

爲什麼要用虛擬機?

  • 跨平臺
    • CPU指令集不用
    • 操作系統接口不同
  • 效率更高
    • 相對於解釋型語言
  • 抽象層次高,更容易編程
    • 消除指針
    • 不用管理內存
    • ……

這裏寫圖片描述

Class 二進制文件一覽

這裏寫圖片描述

先來直觀的看一下一個class 文件的格式,使用16進制的方式來展示二進制的數據。

這裏寫圖片描述

看起來很亂的一個個字節其實是有嚴格次序的, 這些次序就是java 虛擬機所規定的U4, U2中的 U 指的是無符號數 U4 就是4個字節, U2 就是2個字節。先簡單介紹下部分區域的作用,以後會繼續寫專題博客。常量池:的作用是存放 類名,方法名,超類,字段名等。你可能會有疑問類名爲什麼保存? C++編譯地址,而java是動態鏈接,每次都是通過名稱來找類。屬性:jvm非常重要的一部分描述方法中或字段的信息 metadata(元數據),元數據就是描述數據的數據。

魔術與版本號,常量池個數

這裏寫圖片描述

  • Magic Number

    • 每個class文件的頭4個字節稱爲魔術(Magic Number),它的唯一作用就是確定這個文件是否爲一個能被虛擬機接受的Class文件。
  • Major/Minor Version : 版本號

  • 16進制
    Major Version (0x34) = 52
  • 常量池個數 (0x36) = 54
    • 大端模式(Big-Endian):高位在前
    • 00 36 vs 36 00

注意:小端模式: 低位在前。JVM用的是大段模式.物理的CPU用的可能是小端模式, 所以JVM與底層交互時地址值需要做轉換。

常量池

這裏寫圖片描述

想象一下, 我們解析這個class的時候,該怎麼處理? 遇到了”07”,我們知道這是一個class info , 就去取後面的兩個字節當成index , 遇到01 就知道這是一個UTF8Info …,這些數值在JAVA虛擬機規範中都有定義。

注意:與JAVA中語言習慣不同的是,這個容量計數器是從1而不是0開始的,如上圖,常量池容量爲0x0036,即十進制的54,這就代表常量池中有53項常量,索引值的範圍爲1~53。

常量池例子

這裏寫圖片描述

剛纔咱們看到了常量池中的兩項: Class Info , UTF8 Info , 這裏列出了更多的常量項, 注意我們最最常用的幾個 MethodRef, NameAndType, FieldRef注意他們之間的關係。詳細信息請參考JAVA虛擬機規範。你可能會注意到以下這些特殊字符 I : int , L : 代表類, [ : 一維數組, ()V:沒有返回值的方法。

幾種最常見的結構

CONSTANT_Fieldref_info {       
    u1 tag;       //值爲9
    u2 class_index;       
    u2 name_and_type_index; 
} 

CONSTANT_Methodref_info {    
    u1 tag;         //值爲10
    u2 class_index;       
    u2 name_and_type_index; 
}

CONSTANT_NameAndType_info {  
    u1 tag;     //值爲12
    u2 name_index;  
    u2 descriptor_index; 
}

注意:詳情請參見《java虛擬機規範》

訪問標誌(Access Flags) : U2

這裏寫圖片描述
這個標誌用於識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義爲public類型;是否定義爲abstract類型;如果是類的話,是否被聲明爲final等。

類索引(U2) 、父類索引(U2)

這裏寫圖片描述

思考問題:爲什麼需要記錄this class?

字段(Field)

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

這裏寫圖片描述

u2 fields_count;   // 有多少個字段

field_info {  
    u2 access_flags;   // 例如是public , private 等等
    u2 name_index;   // 指向常量池的入口
    u2 descriptor_index;   //指向常量池的入口
    u2 attributes_count;   // 該字段的屬性有多少個
    attribute_info attributes[attributes_count];  //屬性信息
}

標誌字符含義

這裏寫圖片描述

方法(method)

這裏寫圖片描述
例子:左邊是JVM中的信息 右邊是源代碼(參數名省略用”xx”)
(Ljava/lang/String;)V —> void( String xx)
(Ljava/lang/String;IF)V —> void ( String xx, int xx, float xx )

屬性(class 文件中最複雜的部分)

  • 截至Java SE7, 已經有21個屬性

  • 方法和字段都可能有屬性

    • 例如:方法中有Code屬性, 字段有Constant Value屬性
  • 屬性中可能有嵌套屬性

    • Code屬性中還有Line Number Table, Local Variable Table,Stack Map Table 等屬性
  • 可以自定義屬性

Constant Value

如果某字段爲靜態類型( access_flags 中包含 ACC_STATIC 標誌),
將會被分配 ConstantValue 屬性

ConstantValue_attribute {  
    //必須是一個對常量池的有效索引。常量池在該索引處的項必須是UTF8Info,表示字符串“ConstantValue”。
    u2 attribute_name_index;   
    //固定爲2
    u4 attribute_length;  
    //必須是一個對常量池的有效索引。常量池在該索引處的項給出該屬性表示的常量值, 可能的值有Constant_String, Constant_Long…….
    u2 constantvalue_index; 
}

Code屬性

Code_attribute {  
  u2 attribute_name_index;  //指向常量池,應該是UTF8Info ,值爲”Code”
  u4 attribute_length;             //屬性長度, 不包括開始的6個字節
  u2 max_stack;                       // 操作數棧的最大深度(注:編譯時已經確定)
  u2 max_locals;                      // 最大局部變量表個數
  u4 code_length;                    // 該方法的代碼長度
  u1 code[code_length];         //真正的字節碼
  u2 exception_table_length;    
  { 
        u2 start_pc;   
        u2 end_pc;   
        u2 handler_pc;   
        u2 catch_type;  
} exception_table[exception_table_length];  
  u2 attributes_count;  
  attribute_info attributes[attributes_count]; 
} 

Code屬性中的字節碼

這裏寫圖片描述

真正的字節碼就是一個個字節而已。但是,在執行的過程中需要把他們拆解成操作碼和操作數兩個部分, 特別需要注意的是, 每個操作碼所需要的操作數不一定一樣! , 有的沒有操作數(2A , aload_0), 有的有一個(10 , 1E -> bipush 30),有的有兩個,例如 A2 00 0E (if_icmp_ge 20)對於操作數而言, 有的就是立即數,例如goto 28 , bipush 30 , 有的是指向常量池的索引。每個操作碼的具體含義可以在《Java虛擬機規範》中找到

LineNumberTable屬性

LineNumberTable_attribute {

  u2 attribute_name_index;

  u4 attribute_length;

  u2 line_number_table_length;
  { 
    u2 start_pc;    // 字節碼偏移量
    u2 line_number;   //行號
  } line_number_table[line_number_table_length];

}

可選的變長屬性, 描述Java 源碼行號與字節碼行號(字節碼偏移量)之間的對應關係, 調試器可以使用, 屬於Code屬性

LocalVariableTable屬性

LocalVariableTable_attribute {

  u2 attribute_name_index;

  u4 attribute_length;

  u2 local_variable_table_length;

  {
    u2 start_pc;    //局部變量位於 [start_pc, start_pc+length) 之間
    u2 length;   
    u2 name_index;    //局部變量的名稱索引
    u2 descriptor_index;  //局部變量的描述符索引
    u2 index;                      //局部變量在棧幀中的索引
  } local_variable_table[local_variable_table_length]; 
}

是可選變長屬性,描述棧幀中局部變量表中的變量和java 源碼中定義的變量之間的關係, 屬於Code屬性

運行時結構圖

這裏寫圖片描述
每個程序運行起來至少都有一個線程,每個線程都有一個函數幀。JVM細分了函數棧。操作數棧的作用:java是基於棧的虛擬機,例如想把兩個數進行相加的話必須把操作數A壓入棧,與操作數B壓入棧,然後把兩個數相加再壓入棧,然後再彈出棧。操作數棧與局部變量都會引用堆上的對象。常量池的引用在方法區,方法區存放了這個類的數據。

一個例子

編譯前與編譯後

這裏寫圖片描述

執行時函數棧幀

這裏寫圖片描述

0:aload_0 說明把局部變量表第0哥元素壓入棧

形成新的函數棧幀

這裏寫圖片描述

先把demo棧幀的三個數copy到新棧幀的局部變量表中,然後開始執行add操作數棧。
什麼是動態鏈接?名稱與對象對應起來,方便以後的操作。
有三個概念需要清楚:

常量池(Constant Pool):常量池數據編譯期被確定,是Class文件中的一部分。存儲了類、方法、接口等中的常量,當然也包括字符串常量。

字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存儲編譯期類中產生的字符串類型數據。

運行時常量池(Runtime Constant Pool): 方法區的一部分,所有線程共享。虛擬機加載Class後把常量池中的數據放入到運行時常量池。

部分內容參考自:
《深入理解java虛擬機》
《JAVA虛擬機規範》
 Wanna的博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章