中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JVM之Class類文件結構的示例分析

發布時間:2021-08-17 11:55:20 來源:億速云 閱讀:129 作者:小新 欄目:編程語言

這篇文章主要為大家展示了“JVM之Class類文件結構的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“JVM之Class類文件結構的示例分析”這篇文章吧。

具體如下:

概述

我們平時在DOS界面中往往需要運行先運行javac命令,這個命令的直接結果就是產生相應的class文件,然后基于這個class文件才可以真正運行程序得到結果。自然。這是Java虛擬機的功勞,那么是不是Java虛擬機只能編譯.java的源文件呢?答案是否定的。時至今日,Java虛擬機已經實現了語言無關性的特點。而實現語言無關性的基礎是虛擬機和字節碼的存儲格式,Java虛擬機已經不和包括Java語言在內的任何語言綁定。它只與“class”文件這種特定的二進制文件相關聯。在class文件中包含了Java虛擬機指令集和符號表以及若干輔助信息。可以很容易想到Java(本質上不是Java語言本身的平臺無關性,而是其底層的Java虛擬機的平臺無關性使然。)的跨平臺,因為任何一門功能性語言都可以表示為能被Java虛擬機接受的有效的class文件。比如,除了Java虛擬機可以將Java源文件直接編譯為class文件外,使用JRuby等其他語言的編譯器一樣可以把程序代碼編譯成class文件,由此可見,Java虛擬機并不關心class文件是由何種語言編譯來的。

Class類文件結構

Class文件是一組以8字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊排列在class文件中,中間沒有任何分隔符,這使得class文件中存儲的內容幾乎是全部程序運行的程序。Java虛擬機規范規定,Class文件格式采用類似C語言結構體的偽結構來存儲數據,這種結構只有兩種數據類型:無符號數和表。

無符號數屬于基本數據類型,主要可以用來描述數字、索引符號、數量值或者按照UTF-8編碼構成的字符串值,大小使用u1、u2、u4、u8分別表示1字節、2字節、4字節和8字節。

表是由多個無符號數或者其他表作為數據項構成的復合數據類型,所有的表都習慣以“_info”結尾。那么表是干嘛的呢?表主要用于描述有層次關系的復合結構的數據,比如方法、字段。需要注意的是class文件是沒有分隔符的,所以每個的二進制數據類型都是嚴格定義的。具體的順序定義如下:

JVM之Class類文件結構的示例分析

在class文件中,主要分為魔數、Class文件的版本號、常量池、訪問標志、類索引(還包括父類索引和接口索引集合)、字段表集合、方法表集合、屬性表集合。

魔數與Class文件版本號

頭4個字節是魔數,魔數的唯一作用在于確定這個Class文件是否是Java虛擬機接受的Class文件。如gif和jpeg等在文件頭中都存在魔術,使用魔術而不是使用擴展名是基于安全性考慮的——擴展名可以隨意被改變。Class文件的魔術值為“0xCAFEBABE”(咖啡寶貝?)。

緊接著魔數的4個字節是Class文件版本號:版本號又分為次版本號和主版本號。其中前兩個字節用于表示次版本號,后兩個字節用于表示主版本號。這個的版本號是隨著jdk版本的不同而表示不同的版本范圍的。如果Class文件的版本號超過虛擬機版本,將被拒絕執行。

常量池

常量池可以簡單理解為class文件的資源從庫,這種數據類型是Class文件結構中與其他項目關聯最多的數據類型,也是占用Class文件空間最大的項目之一。在常量池中主要存放字面量符號引用。字面量比較接近Java語言層面的常量概念,比如文本字符串、聲明為final的常量值等(百度百科的解釋是字面量是用雙引用號引住的一系列字符)。符號引用則主要包括三類常量:

  • 類和接口的全限定名

  • 字段的名稱和描述符

  • 方法的名稱和描述符。

符號引用與直接引用的關聯

  • 符號引用是一組符號,用來描述所引用的目標,符號是以任何形式存在的字面量。對于符號引用Java虛擬機并沒有嚴格的限制。規定只需要使用的時候能夠無歧義定位到目標就可以。常量池存在于Class文件中,而Class文件是必須首先通過Java虛擬機的類加載機制加載到內存中(確切的說是方法區這個內存區域,回顧一下,方法區存放的主要是對象的實例,這個Class文件是虛擬機對外接受訪問的接口)。符號引用屬于常量池中的內容,那么是不是說符號引用的目標已經加載到內存中了呢?答案是否定的,因為符號引用與虛擬機的內存布局無關,符號引用的目標并不一定已經加載到內存中了。

  • 直接引用可以是直接指向引用目標的指針、相對偏移量或者是一個能夠間接定位到目標的句柄。直接引用是和虛擬機的內存布局有關的,同一個符號引用在不同的虛擬機上翻譯的直接引用一般是不同的。如果有了直接引用,那么引用的目標必定是存在內存中的。

在常量池中每一項常量都是一個表,在jdk1.7中共有14中常量類型,所以常量池的項目就對應14張表,這14張表的每種類型都不一樣。但是有一個共同特點:表開始的第一位都是一個u1類型的標志位,代表這個常量屬于哪種類型。

JVM之Class類文件結構的示例分析

需要注意的是,在Class文件中,方法、字段都需要引用CONSTANT-Utf8_info類型的常量,所以這種類型的常量的長度有一定的限制,也就是Java中方法、字段的最大長度。在CONSTANT-Utf8_info中,其length的值u2,說明Java虛擬機只能編譯最大大約64KB的變量或者方法名。超過的話將不會進行編譯。

訪問標志

常量池之后的數據結構是訪問標志(access_flags),這個標志主要用于識別一些類或者接口層次的訪問信息,主要包括:這個Class是類還是接口、是否定義public、是否定義abstract類型;如果是類的話是否被聲明為final等。具體的標志訪問如下:

JVM之Class類文件結構的示例分析

類索引、父類索引和接口索引集合

這個數據項主要用于確定這個類的繼承關系

其中類索引和父類索引都是一個u2類型的數據,而接口索引集合是一組u2類型的數據。在Java中由于不允許多繼承,所以父類索引是唯一的,但是一個類可以實現多個接口,所以得到的接口索引是一個集合,表示這個類實現了哪些接口。

字段表集合

字段表用于描述接口或者類中聲明的變量。

字段包括類級變量和實例級變量,但是不包括方法內部聲明的局部變量(這些變量是存儲在Java虛擬機棧中的局部變量表中的)。自然,描述一個字段的信息包括:字段的作用域(public、protected、private)、實例變量與否(static)、可變性(final)、并發可見性(volatile)、可否被序列化(transient)、字段數據類型(基本數據類型、對象、數組)、字段名稱。字段的信息也被存放在一張表中,其字段表包括三種類型:

  • u2類型訪問標志(access_flags),其訪問標志在access_flags中

  • u2類型的name_index(字段的簡單名稱)

  • u2類型的描述符(descriptor_index)

上面出現了簡單名稱,上文中出現了全限定名,以及這里出現的描述符,三者有什么區別呢?其中全限定名稱比較好理解,就是類的完整路徑信息。而簡單名稱則是指沒有類型和參數修飾的方法或者字段名稱,比如一個方法如下:

public void inc(int a,int b){
  System.out.println(a+b);
}

那么這個方法的簡單名稱就是inc。

相對于以上兩者,描述符相對復雜一些。描述符的主要的作用是描述字段的數據類型、方法的參數列表和返回值。其中我們熟悉的void,在Class文件中用V表示。下面是完整的描述符標志的含義:

JVM之Class類文件結構的示例分析

對于數組類型,每一維度使用一個前置的“[”字符描述,如果是二維數組,那么就有兩個“[”符號。比如“java.lang.String[][]”會被記錄成“[[Ljava.lang.String;”

對于方法,則是按照縣參數列表后返回值的順序進行描述的。比如方法int inc(int a,int[] b,char[][] c,int d)的描述符是“(I[I[[CI)I”。

方法表集合

JVM中堆方法表的描述與字段表是一致的,包括了:訪問標志、名稱索引、描述符索引、屬性表集合。方法表的結構與字段表是一致的,區別在于訪問標志的不同。在方法中不能用volatile和transient關鍵字修飾,所以這兩個標志不能用在方法表中。在方法中添加了字段不能使用的訪問標志,比如方法可以使用synchronized、native、strictfp、abstract關鍵字修飾,所以在方法表中就增加了相應的訪問標志。

要注意的是,如果父類方法沒有在子類中重寫,那么在方法中不會自動出現來自父類的方法信息。同樣的,有可能添加編譯器自動增加的方法,比如方法。

屬性表集合

前面的Class文件、字段表和方法表都可以攜帶自己的屬性信息,這個信息用屬性表進行描述,用于描述某些場景專有的信息。在屬性表中沒有類似Class文件的數據項目類型和順序的嚴格要求,只要新的屬性不與現有的屬性名重復,任何人都可以向屬性表中寫入自己定義的屬性信息。

Code屬性

Java程序方法體中的代碼經過javac編譯最終編譯成的字節碼指令就保存在Code屬性中。但是并非所有的方法表都必須存在這個屬性。Code屬性是Class文件中最重要的一個屬性,如果把一個Java程序中的信息分為代碼(Code)和元數據(Metadata,包括類、字段、方法定義及其其他信息)兩部分,那么在整個Class文件中,Code屬性用于描述代碼,所有其他的數據項目都用于描述元數據。

Exceptions屬性

這個屬性的作用是列舉出方法中可能拋出的受查異常(Checked Exception),也就是描述throws 后的列舉的異常

LineNumberTable屬性

主要用于描述Java源代碼行號與字節碼行號之間的對應關系。這個屬性也不是必須的。如果沒有這個屬性,對程序的直接影響就是當拋出異常的時候無法顯示對應的行號;并且在調試的時候無法通過設置斷點的方法是調試程序。

LocalVariableTable屬性

用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量的之間的關系。也不屬于必須的屬性。如果沒有這個屬性,產生的直接影響就是當別人引用這個方法的時候,所有的參數名稱都會丟失,IDE將會使用諸如args0、args1之類的參數進行顯示。自然,當調試程序的時候,顯示的參數名稱是不可知的。

SourceFile屬性

用于記錄這個Class文件的源碼文件名稱。如果不使用這個屬性,那么當拋出異常的時候,堆棧中將不會顯示出錯代碼所屬的文件名。

ConstantValue屬性

作用是通知虛擬機自動為靜態變量賦值。要注意的是,只有被static關鍵字修飾的額變量才可以使用這個屬性(類變量)。對于非類變量,初始化是在方法中進行的;對于類變量可以選擇兩種方式進行變量的初始化:一是在類構造器方法中使用;二是是ConstantValue屬性。目前Sun Hotspot的選擇原則是:如果一個變量同時使用static和final關鍵字修飾,并且這個變量是基本數據類型或者java.lang.String類型的話,就使用ConstantValue屬性進行初始化。如果沒有被final修飾或者并非是基本數據類型,那么將會選擇使用方法進行初始化。

InnerClass屬性

這個屬性主要用于記錄內部類與宿主類之間的關聯關系。

Deprecated以及Synthetic屬性

這兩個屬性都屬于標志類型的布爾屬性,只存在有沒有的區別。

Deprecated屬性用于表示某個類、字段或者方法,已經被程序作者定為不再推薦使用,可以通過注解@deprecated實現

Synthetic屬性代表此字段并不是由Java源碼產生的,而是通過編譯器自行添加的。

StackMapTable屬性

該屬性的目的在于代替以前比較消耗性能的基于數據流分析的類型推導驗證器。

Signature屬性

這個屬性是專門用來記錄泛型類型的,因為在Java語言采用的是擦除法實現的泛型,在字節碼(Code屬性)中,泛型信息編譯之后會被擦除。擦除法的優點是能夠節省泛型所占的內存空間,缺點是在運行期間無法通過反射得到泛型信息,而Signature屬性則彌補了這一缺陷。現在的Java反射API已經能夠得到泛型信息,功勞就在于這個屬性。

BootstrapMethods屬性

這個屬性用于保存invokedynamic指令引用的引導方法限定符。該指令用于在運行時動態解析出調用點限定符所引用的方法,并執行該方法。

以上是“JVM之Class類文件結構的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

株洲市| 万载县| 宜良县| 大新县| 天津市| 疏勒县| 台山市| 西青区| 南漳县| 辽宁省| 西乌珠穆沁旗| 乌拉特中旗| 台安县| 四会市| 乌兰浩特市| 潼关县| 岢岚县| 诏安县| 高唐县| 新田县| 黔东| 延吉市| 宕昌县| 芜湖市| 宜黄县| 天全县| 万州区| 赤壁市| 仙居县| 邯郸市| 崇州市| 合肥市| 漾濞| 成武县| 伊通| 邻水| 凌海市| 德保县| 涿鹿县| 东源县| 翼城县|