国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java虛擬機:Java二進制字節碼的結構、加載

CHENGKANG / 1891人閱讀

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

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

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

我們在文件里寫入了java的源代碼,源代碼寫就后存入磁盤,磁盤上的源代碼經過javac命令的編譯形成了二進制字節碼形成了class文件,經過一番步驟后java虛擬機將這些二進制字節碼按照一定的方式讀入內存中的不同區域形成了二進制字節碼的活化狀態,虛擬機使用字節碼指定的命令執行這些指令,其間使用字節碼中存儲的數據,最終完成了任務。這個過程就是java虛擬機執行java二進制字節碼的過程的簡單概括。可以如下圖所示:

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

java源代碼轉換的結果:編譯所得到字節碼的結構

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

這個龐大的結構主要包含以下幾個部分:

1.魔數和版本號

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

2.常量池

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

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

3.訪問標志

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

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

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

5.字段表集合

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

6.方法表集合

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

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

7.類其它屬性表

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

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

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

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字節碼轉換為活化的內存數據:類加載的過程

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

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

加載的任務:

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

類加載器:加載的時候是根據全限定名去找到對應的二進制字節碼,這個過程是由類加載器完成的,雖然是同一個二進制字節碼文件,如果類加載器的選擇不同,那么出于安全考慮也不能判定加載出的類是完全一致的,因此自定義加載器和系統的應用程序加載器對同一個類的加載結果是不一樣的,要判定這兩個類是不一樣的。為了解決這個問題,類加載器被設計成了多層繼承關系,從上向下分別是啟動類加載器、拓展類加載器、應用程序加載器、自定義加載器,加載的時候層層向上代理給父加載器,最終將會使得啟動類加載器執行最終的加載,以確保所有同名的類能夠被同一個類加載器所加載。

驗證的任務:

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

準備的任務:

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

解析的任務:

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

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

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

初始化的任務:

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

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

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

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

反射調用的時候初始化類;

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

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66170.html

相關文章

  • Java虛擬Java字節碼的編譯生成和運行優化

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

    Hwg 評論0 收藏0
  • Java虛擬Java字節碼指令的執行

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

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

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

    hqman 評論0 收藏0
  • 字節碼及ASM使用

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

    hearaway 評論0 收藏0
  • 我的面試準備過程--JVM相關

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

    Towers 評論0 收藏0

發表評論

0條評論

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