溫馨提示×

溫馨提示×

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

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

JVM的Class類文件結(jié)構(gòu)是怎樣的

發(fā)布時間:2022-01-11 09:31:56 來源:億速云 閱讀:84 作者:iii 欄目:編程語言

這篇文章主要介紹“JVM的Class類文件結(jié)構(gòu)是怎樣的”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“JVM的Class類文件結(jié)構(gòu)是怎樣的”文章能幫助大家解決問題。

概述

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

Class類文件結(jié)構(gòu)

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

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

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

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

魔數(shù)與Class文件版本號

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

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

常量池

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

類和接口的全限定名  字段的名稱和描述符  方法的名稱和描述符。

符號引用與直接引用的關(guān)聯(lián)

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

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

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

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

訪問標志

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

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

這個數(shù)據(jù)項主要用于確定這個類的繼承關(guān)系。

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

字段表集合

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

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

u2類型訪問標志(access_flags),其訪問標志在access_flags中  u2類型的name_index(字段的簡單名稱)  u2類型的描述符(descriptor_index)

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

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

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

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

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

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

方法表集合

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

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

屬性表集合

前面的Class文件、字段表和方法表都可以攜帶自己的屬性信息,這個信息用屬性表進行描述,用于描述某些場景專有的信息。在屬性表中沒有類似Class文件的數(shù)據(jù)項目類型和順序的嚴格要求,只要新的屬性不與現(xiàn)有的屬性名重復,任何人都可以向?qū)傩员碇袑懭胱约憾x的屬性信息。

Code屬性

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

Exceptions屬性

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

LineNumberTable屬性

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

LocalVariableTable屬性

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

SourceFile屬性

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

ConstantValue屬性

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

InnerClass屬性

這個屬性主要用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)關(guān)系。

Deprecated以及Synthetic屬性

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

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

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

StackMapTable屬性

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

Signature屬性

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

BootstrapMethods屬性

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

關(guān)于“JVM的Class類文件結(jié)構(gòu)是怎樣的”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細節(jié)

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

AI