成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java虛擬機:Java二進制字節(jié)碼的結(jié)構(gòu)、加載

CHENGKANG / 1918人閱讀

摘要:這個龐大的結(jié)構(gòu)主要包含以下幾個部分魔數(shù)和版本號基本的信息用于確定二進制字節(jié)碼的特征和加載可行特征。

這篇文章的素材來自周志明的《深入理解Java虛擬機》。

作為Java開發(fā)人員,一定程度了解JVM虛擬機的的運作方式非常重要,本文就一些簡單的虛擬機的相關(guān)概念和運作機制展開我自己的學習過程,是這個系列的第二篇。

我們在文件里寫入了java的源代碼,源代碼寫就后存入磁盤,磁盤上的源代碼經(jīng)過javac命令的編譯形成了二進制字節(jié)碼形成了class文件,經(jīng)過一番步驟后java虛擬機將這些二進制字節(jié)碼按照一定的方式讀入內(nèi)存中的不同區(qū)域形成了二進制字節(jié)碼的活化狀態(tài),虛擬機使用字節(jié)碼指定的命令執(zhí)行這些指令,其間使用字節(jié)碼中存儲的數(shù)據(jù),最終完成了任務。這個過程就是java虛擬機執(zhí)行java二進制字節(jié)碼的過程的簡單概括??梢匀缦聢D所示:

這只是對這個過程的簡單介紹,實際上其中的每一步都至關(guān)重要而且復雜,正是這些過程最終使得我們編寫的java源代碼能夠運行在虛擬機搭建的環(huán)境中。

java源代碼轉(zhuǎn)換的結(jié)果:編譯所得到字節(jié)碼的結(jié)構(gòu)

java的二進制字節(jié)碼是一個緊密連接的二進制數(shù)碼,這個數(shù)碼的結(jié)構(gòu)如下,各個結(jié)構(gòu)之間是無縫連接的,也因此首先于這種規(guī)則,java的二進制代碼才不會產(chǎn)生二義性,即虛擬機在讀區(qū)這些數(shù)碼時可以唯一地解析出它所表達的意思。

這個龐大的結(jié)構(gòu)主要包含以下幾個部分:

1.魔數(shù)和版本號

基本的信息用于確定java二進制字節(jié)碼的特征和加載可行特征。
魔數(shù)“CAFEBABE”用以確定這段字節(jié)碼是java字節(jié)碼的開始,版本號用于確定不同版本的jdk編譯了不同版本的java源代碼生成了不同版本的二進制字節(jié)碼,這個標記的另外的目的用于提示虛擬機高于當前版本的二進制字節(jié)碼可能由于兼容性不能加載。

2.常量池

所有和程序相關(guān)的常量都將加入這個部分中,這個部分開頭的常量數(shù)決定了常量池中常量的個數(shù)以使得虛擬機能夠正確解析出哪些部分是常量池。后面的常量以表的形式呈現(xiàn),“表”是字節(jié)碼中一個特殊的復合型的數(shù)據(jù)結(jié)構(gòu),不同類型的常量有不同的標記tag以指示虛擬機以不同的方式解析出常量的值。這樣最終虛擬機將根據(jù)不同類型的常量解析出常量池中的全部常量對應的值或索引。

常量分為字面量和符號引用兩種,字面量即一般的基本類型的數(shù)據(jù),比如整型、浮點型等,而符號引用則是那些需要進一步通過這個符號的值去尋找它真正引用的對象,比如CONSTANT_Fieldref_info類型的常量就是符號引用,必須通過這個字段名去尋找到它真正引用的字段。
如下是常量池中的常量類型,另外以CONSTANT_Utf8_info表為例說明了常量表中的結(jié)構(gòu):

3.訪問標志

關(guān)乎類的訪問權(quán)限的信息將會以位的不同的形式展示在這里。
以下是訪問標志的不同位,如果有好幾個訪問標志,那么一般將它們做或運算將幾個相關(guān)的位都展示出來。

4.類索引、父類索引和接口索引

這些字節(jié)碼向虛擬機提供了這個類的類名、父類的類名和接口名的索引值,這個索引值最終將可以從常量池中獲得其對應的全限定名。

5.字段表集合

(成員變量的描述)這些字節(jié)碼向虛擬機提供了這個類中包含的字段的個數(shù)和每個字段的信息,每個字段同樣是用一個字段表來描述的,這個字段表里說明了這個字段的信息:字段的訪問權(quán)限、名索引在常量池中找到它的名字、描述符說明了這個字段的類型,可能會附帶的屬性表則會進一步通過拓展的數(shù)據(jù)結(jié)構(gòu)展示這個字段的其它屬性,比如這個字段可能被賦的初值。
以下展示的是字段表的結(jié)構(gòu):

6.方法表集合

(成員方法的描述)和字段表類似的,這些字節(jié)碼向虛擬機提供了這個類中包含的方法的個數(shù)和每個方法的信息,,每個方法用一個方法表來描述:方法的訪問權(quán)限、方法名的索引在常量池中找到這個方法的名字、描述符索引得到了這個方法的特征如返回值類型和參數(shù),可能會附帶的屬性表則會進一步通過拓展的數(shù)據(jù)結(jié)構(gòu)展示這個方法的其它屬性,比如這個方法索引得到的Code屬性存在的話那么說明這個方法的方法體是存在的,則接下去的字節(jié)碼就是具體的方法體了,這個方法體由Code屬性表來描述。Code屬性表則是更深入的一個數(shù)據(jù)結(jié)構(gòu)了(字節(jié)碼的數(shù)據(jù)結(jié)構(gòu)就是以這樣可拓展的方式一步步建立的,當簡單的索引或字面量不足以描述的時候就會引入表,以結(jié)構(gòu)化的方式來對所要描述的對象做進一步的闡釋),在Code屬性表里規(guī)定了“Code”常量索引以確定這段字節(jié)碼是方法體、Code屬性長度、最大棧、局部變量空間、代碼長、代碼、異常數(shù)和異常表,還有可能帶有其他可拓展的屬性表。

以下是方法表的結(jié)構(gòu),針對方法表中的Code屬性表可以看到它的更深一層的結(jié)構(gòu),方法表中還有其他的屬性表可依據(jù)情況以供拓展,比如Exceptions屬性表用以描述這個方法所要規(guī)定的可拋出異常。

7.類其它屬性表

基于同樣的拓展思想,整體結(jié)構(gòu)最后也預留了同樣的屬性表來做拓展,包括源代碼所在文件等信息都可以拓展在這個部分里。

這個部分我們可以清楚地看出字節(jié)碼設(shè)計者對于數(shù)據(jù)結(jié)構(gòu)的可拓展性的追求,通過可拓展的屬性表的定義,很多難以描述的結(jié)構(gòu)可以更深一步的描述(這有點像是文件的結(jié)構(gòu):在一個文件難以描述的時候就用一個包含了很多文件的文件夾來共同描述),這種設(shè)計最終使java二進制字節(jié)碼能夠長期穩(wěn)定的存在下來,因為新添加的特性只需要在特定的節(jié)點做一個拓展即可。

上面的部分除了辛苦地使用十六進制編輯器對class文件作分析之外還可以直接使用jdk提供的javap工具進行分析:javap -verbose * ,它將結(jié)構(gòu)化的結(jié)果呈現(xiàn)出來:

jinhaoplus$ javap -verbose MyClass
Classfile /Users/jinhao/Desktop/MyClass.class
  Last modified 2015-10-11; size 288 bytes
  MD5 checksum 8235b2e50d3ca6704b44862387570773
  Compiled from "MyClass.java"
class MyClass
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#17         // java/lang/Object."":()V
   #2 = String             #18            // a
   #3 = Fieldref           #4.#19         // MyClass.x:Ljava/lang/String;
   #4 = Class              #20            // MyClass
   #5 = Class              #21            // java/lang/Object
   #6 = Utf8               x
   #7 = Utf8               Ljava/lang/String;
   #8 = Utf8               ConstantValue
   #9 = Utf8               y
  #10 = Utf8               C
  #11 = Utf8               
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               SourceFile
  #16 = Utf8               MyClass.java
  #17 = NameAndType        #11:#12        // "":()V
  #18 = Utf8               a
  #19 = NameAndType        #6:#7          // x:Ljava/lang/String;
  #20 = Utf8               MyClass
  #21 = Utf8               java/lang/Object
{
  final java.lang.String x;
    descriptor: Ljava/lang/String;
    flags: ACC_FINAL
    ConstantValue: String a

  char y;
    descriptor: C
    flags:

  MyClass();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: aload_0
         5: ldc           #2                  // String a
         7: putfield      #3                  // Field x:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 1: 0
        line 2: 4
}
SourceFile: "MyClass.java"

java字節(jié)碼轉(zhuǎn)換為活化的內(nèi)存數(shù)據(jù):類加載的過程

java的字節(jié)碼經(jīng)過了編譯存在了磁盤上,那么把它從磁盤里請到內(nèi)存里成為真正活化可用的內(nèi)存對象是至關(guān)重要的,這個過程稱之為類加載過程:Class Loading,這個加載過程結(jié)束后class文件里的二進制字節(jié)碼將會成為內(nèi)存不同區(qū)域里的數(shù)據(jù),虛擬機就可以按照原則將這些數(shù)據(jù)代表的指令執(zhí)行完成任務。

上圖展示的就是這個過程的分步驟。下面是這幾步的功能:

加載的任務:

加載就是把二進制字節(jié)碼轉(zhuǎn)為字節(jié)流,通過類的全限定名把對應class文件里的二進制字節(jié)碼轉(zhuǎn)換為虛擬機內(nèi)存中方法區(qū)里規(guī)定的數(shù)據(jù)結(jié)構(gòu)(也就是說最終虛擬機里的結(jié)構(gòu)并不是二進制字節(jié)碼的那種緊密型)以完成之后的數(shù)據(jù)向內(nèi)存中的分配,同時在堆內(nèi)存中開辟區(qū)域以存放類的java.lang.Class對象以使得將來形成的方法區(qū)中的數(shù)據(jù)能有入口訪問。

類加載器:加載的時候是根據(jù)全限定名去找到對應的二進制字節(jié)碼,這個過程是由類加載器完成的,雖然是同一個二進制字節(jié)碼文件,如果類加載器的選擇不同,那么出于安全考慮也不能判定加載出的類是完全一致的,因此自定義加載器和系統(tǒng)的應用程序加載器對同一個類的加載結(jié)果是不一樣的,要判定這兩個類是不一樣的。為了解決這個問題,類加載器被設(shè)計成了多層繼承關(guān)系,從上向下分別是啟動類加載器、拓展類加載器、應用程序加載器、自定義加載器,加載的時候?qū)訉酉蛏洗斫o父加載器,最終將會使得啟動類加載器執(zhí)行最終的加載,以確保所有同名的類能夠被同一個類加載器所加載。

驗證的任務:

畢竟是文件里的字節(jié)碼,沒有辦法保證字節(jié)碼是不被修改的安全代碼,即使保證了沒被修改也不能保證代碼編寫者在滿足編譯成功之外沒有犯下低級的語意錯誤,所以對字節(jié)碼的驗證工作至關(guān)重要,可以一定程度上保證字節(jié)碼的安全性和正確性,虛擬機將從字節(jié)流的格式是否正確(是否滿足class字節(jié)碼的格式限制)、元數(shù)據(jù)語法是否正確(類的元數(shù)據(jù)信息是否符合java語言的語法要求)、字節(jié)碼安全性是否保證(是否有跨越內(nèi)存安全性的錯誤和隱患出現(xiàn))、符號引用驗證是否能夠通過(符號引用是查看那些非類自身的其它類和這些類中的字段和方法是否真的存在,這個過程是解析時會觸發(fā)的,解析的過程會去查看這些符號引用到的類的情況是否會出錯)。

準備的任務:

準備是為了在方法區(qū)中為即將要分配內(nèi)存的數(shù)據(jù)開始開辟在相應位置開辟內(nèi)存空間,并將相應的字節(jié)流注入到這些空間中去,同時為字段賦初值。值得注意的是,除非字段帶有Constant Value屬性外,一般情況下賦初值的時候都會為字段賦零值。這個過程結(jié)束后方法區(qū)里就已經(jīng)建立起來了類的基本數(shù)據(jù)結(jié)構(gòu),這其中包括常量池的常量。

解析的任務:

準備階段結(jié)束后類變量就都帶著初值在方法區(qū)中等待了,但是這個時候方法區(qū)中的常量池里的常量卻只是一些字面量和符號引用,字面量是可以直接使用的,但是符號引用必須轉(zhuǎn)換為直接引用(可以理解為這些引用真正指向的地址)才能使用,否則這些常量的字面量并不能指定活化在內(nèi)存里的對象:比如常量池中有一個CONSTANT_Class_info類型的符號引用常量,這個類符號引用里存儲的僅僅是類的全限定名的索引,找到全限定名之后也沒有什么用處,因為沒辦法確定符合這個全限定名的類在內(nèi)存中加載的具體地址,因此必須將這個符號引用對應的類的直接引用(地址)找出來,也就是轉(zhuǎn)換為直接引用。

所以解析的任務就是將常量池里的符號引用轉(zhuǎn)換為直接引用,以使得方法區(qū)里的類、父類、接口、字段、方法能夠通過自身的索引尋找常量池中的引用時直接定位到這些類、父類、接口、字段、方法的準確的內(nèi)存地址。

另外需要注意的是,解析不一定非要在準備之后初始化之前進行,因為我們可以看到這個階段的主要任務是使用階段才會用到的,如果程序中有動態(tài)綁定的需求時這時候是沒有辦法把符號引用準確轉(zhuǎn)換為直接引用的。所以解析的階段有時會在初始化之后甚至使用的過程中才會再進行的。這樣做的好處就是能夠完成相對靈活的動態(tài)綁定。

初始化的任務:

初始化的過程實際上就是執(zhí)行類初始化函數(shù)的過程,這個函數(shù)執(zhí)行的其實就是字段的賦值語句和靜態(tài)代碼塊的執(zhí)行,這步過后,所有的字段都將被初始化為程序中賦值語句和靜態(tài)代碼塊要求的初值,而不是準備階段的零值。

以上幾個步驟就是類加載的全過程,在這個過程中,class文件中的二進制字節(jié)碼以二進制字節(jié)流的形式先按照方法區(qū)特定的數(shù)據(jù)結(jié)構(gòu)重整并建立java.lang.Class對象于堆中,驗證重整后的二進制字節(jié)流沒有語法、語意和安全性的問題后虛擬機為即將加載的類在方法區(qū)中開辟內(nèi)存空間,字節(jié)流注入開辟的方法區(qū)的內(nèi)存空間并將各字段賦零值,常量池中的符號引用轉(zhuǎn)換為有實際意義的直接引用以訪問特定的地址,特定的字段被初始化為程序規(guī)定的初值,整個類成功加載到方法區(qū)中。

虛擬機規(guī)范制定了很多限制,這些限制是必須遵守的,不同的廠商對虛擬機的規(guī)范有不同的實現(xiàn),但是面對限制都是一樣的遵守。java虛擬機對于類加載的時機沒有明確限制,但是對于類加載過程的初始化的時機卻有明確的四個“有且僅有要求”:這些條件下必須對類進行初始化,鑒于類初始化位于類加載過程的最后,所以這個規(guī)定也可以大致理解為類加載的時機,這些時機稱為“主動引用”:

new新對象、讀寫靜態(tài)字段、調(diào)用靜態(tài)方法的時候必須初始化類:讀寫靜態(tài)字段時只初始化這個靜態(tài)字段所在的類,如果是父類的靜態(tài)字段則只初始化父類而不初始化子類;另外如果是static final修飾的靜態(tài)字段,那么在編譯的時候就會將其寫入常量池,這個時候即使讀這個靜態(tài)字段也不會加載類,因為只需要去常量池中取這個值就好。這兩個策略的目的其實都是盡可能地減少類加載的開銷;

反射調(diào)用的時候初始化類;

初始化類的時候如果父類未初始化要初始化父類;

執(zhí)行主類(執(zhí)行的main函數(shù)所在類)要初始化。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/66170.html

相關(guān)文章

  • Java虛擬Java字節(jié)碼的編譯生成和運行優(yōu)化

    摘要:字節(jié)碼生成把語法樹定義的抽象的語法結(jié)構(gòu)按照二進制字節(jié)碼的規(guī)則排布成字節(jié)碼,最終我們可以看到滿足虛擬機運行要求的二進制字節(jié)碼被轉(zhuǎn)換出來。上面的過程完成后,命令扮演的編譯器就將源代碼轉(zhuǎn)成了結(jié)構(gòu)化的二進制字節(jié)碼。 這篇文章的素材來自周志明的《深入理解Java虛擬機》。 作為Java開發(fā)人員,一定程度了解JVM虛擬機的的運作方式非常重要,本文就一些簡單的虛擬機的相關(guān)概念和運作機制展開我自己的學...

    Hwg 評論0 收藏0
  • Java虛擬Java字節(jié)碼指令的執(zhí)行

    摘要:虛擬機執(zhí)行程序的基礎(chǔ)是特定的二進制指令集和運行時棧幀二進制指令集是虛擬機規(guī)定的一些指令,在編譯后二進制字節(jié)碼的類方法里的字節(jié)碼就是這種指令,所以只要找到方法區(qū)里的類方法就可以依照這套指令集去執(zhí)行命令。 這篇文章的素材來自周志明的《深入理解Java虛擬機》。 作為Java開發(fā)人員,一定程度了解JVM虛擬機的的運作方式非常重要,本文就一些簡單的虛擬機的相關(guān)概念和運作機制展開我自己的學習過程...

    coolpail 評論0 收藏0
  • 教你用Java字節(jié)碼做點有趣的事

    摘要:字節(jié)碼是程序的中間表示形式介于人類可讀的源碼和機器碼之間。在中一般是用編譯源文件變成字節(jié)碼,也就是我們的文件。字節(jié)碼的執(zhí)行操作,指的就是對當前棧幀數(shù)據(jù)結(jié)構(gòu)進行的操作。 0.寫在前面 為什么會寫這篇文章呢?主要是之前調(diào)研過日志脫敏相關(guān)的一些,具體可以參考LOG4j脫敏插件如何編寫里面描述了日志脫敏插件編寫方法: 直接在toString中修改代碼,這種方法很麻煩,效率低,需要修改每一個要...

    hqman 評論0 收藏0
  • 字節(jié)碼及ASM使用

    摘要:字節(jié)碼及使用什么是字節(jié)碼機器碼機器碼是可直接解讀的指令。字節(jié)碼的執(zhí)行操作,指的就是對當前棧幀數(shù)據(jù)結(jié)構(gòu)進行的操作。動態(tài)鏈接每個棧幀指向運行時常量池中該棧幀所屬的方法的引用,也就是字節(jié)碼的發(fā)放調(diào)用的引用。 字節(jié)碼及ASM使用 什么是字節(jié)碼? 機器碼機器碼(machine code)是CPU可直接解讀的指令。機器碼與硬件等有關(guān),不同的CPU架構(gòu)支持的硬件碼也不相同。 字節(jié)碼字節(jié)碼(byte...

    hearaway 評論0 收藏0
  • 我的面試準備過程--JVM相關(guān)

    摘要:程序計數(shù)器程序計數(shù)器是一塊較小的內(nèi)存空間,它的作用可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器。它的主要缺點有兩個一個是效率問題,標記和清除過程的效率都不 Jvm 相關(guān)  類加載機制 本段參考 http://www.importnew.com/2374... 類加載概念 類加載指的是將類的.class文件中的二進制數(shù)據(jù)讀入到內(nèi)存中,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個ja...

    Towers 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<