這篇文章主要為大家展示了“JVM之Class類文件結(jié)構(gòu)的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“JVM之Class類文件結(jié)構(gòu)的示例分析”這篇文章吧。
創(chuàng)新互聯(lián)是一家專注于網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站與策劃設(shè)計(jì),定結(jié)網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)10多年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:定結(jié)等地區(qū)。定結(jié)做網(wǎng)站價(jià)格咨詢:13518219792
具體如下:
我們平時(shí)在DOS界面中往往需要運(yùn)行先運(yùn)行javac命令,這個(gè)命令的直接結(jié)果就是產(chǎn)生相應(yīng)的class文件,然后基于這個(gè)class文件才可以真正運(yùn)行程序得到結(jié)果。自然。這是Java虛擬機(jī)的功勞,那么是不是Java虛擬機(jī)只能編譯.java的源文件呢?答案是否定的。時(shí)至今日,Java虛擬機(jī)已經(jīng)實(shí)現(xiàn)了語言無關(guān)性的特點(diǎn)。而實(shí)現(xiàn)語言無關(guān)性的基礎(chǔ)是虛擬機(jī)和字節(jié)碼的存儲格式,Java虛擬機(jī)已經(jīng)不和包括Java語言在內(nèi)的任何語言綁定。它只與“class”文件這種特定的二進(jìn)制文件相關(guān)聯(lián)。在class文件中包含了Java虛擬機(jī)指令集和符號表以及若干輔助信息??梢院苋菀紫氲絁ava(本質(zhì)上不是Java語言本身的平臺無關(guān)性,而是其底層的Java虛擬機(jī)的平臺無關(guān)性使然。)的跨平臺,因?yàn)槿魏我婚T功能性語言都可以表示為能被Java虛擬機(jī)接受的有效的class文件。比如,除了Java虛擬機(jī)可以將Java源文件直接編譯為class文件外,使用JRuby等其他語言的編譯器一樣可以把程序代碼編譯成class文件,由此可見,Java虛擬機(jī)并不關(guān)心class文件是由何種語言編譯來的。
Class文件是一組以8字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊排列在class文件中,中間沒有任何分隔符,這使得class文件中存儲的內(nèi)容幾乎是全部程序運(yùn)行的程序。Java虛擬機(jī)規(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é)。
表是由多個(gè)無符號數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型,所有的表都習(xí)慣以“_info”結(jié)尾。那么表是干嘛的呢?表主要用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),比如方法、字段。需要注意的是class文件是沒有分隔符的,所以每個(gè)的二進(jìn)制數(shù)據(jù)類型都是嚴(yán)格定義的。具體的順序定義如下:
在class文件中,主要分為魔數(shù)、Class文件的版本號、常量池、訪問標(biāo)志、類索引(還包括父類索引和接口索引集合)、字段表集合、方法表集合、屬性表集合。
頭4個(gè)字節(jié)是魔數(shù),魔數(shù)的唯一作用在于確定這個(gè)Class文件是否是Java虛擬機(jī)接受的Class文件。如gif和jpeg等在文件頭中都存在魔術(shù),使用魔術(shù)而不是使用擴(kuò)展名是基于安全性考慮的——擴(kuò)展名可以隨意被改變。Class文件的魔術(shù)值為“0xCAFEBABE”(咖啡寶貝?)。
緊接著魔數(shù)的4個(gè)字節(jié)是Class文件版本號:版本號又分為次版本號和主版本號。其中前兩個(gè)字節(jié)用于表示次版本號,后兩個(gè)字節(jié)用于表示主版本號。這個(gè)的版本號是隨著jdk版本的不同而表示不同的版本范圍的。如果Class文件的版本號超過虛擬機(jī)版本,將被拒絕執(zhí)行。
常量池可以簡單理解為class文件的資源從庫,這種數(shù)據(jù)類型是Class文件結(jié)構(gòu)中與其他項(xiàng)目關(guān)聯(lián)最多的數(shù)據(jù)類型,也是占用Class文件空間最大的項(xiàng)目之一。在常量池中主要存放字面量和符號引用。字面量比較接近Java語言層面的常量概念,比如文本字符串、聲明為final的常量值等(百度百科的解釋是字面量是用雙引用號引住的一系列字符)。符號引用則主要包括三類常量:
類和接口的全限定名
字段的名稱和描述符
方法的名稱和描述符。
符號引用與直接引用的關(guān)聯(lián)
符號引用是一組符號,用來描述所引用的目標(biāo),符號是以任何形式存在的字面量。對于符號引用Java虛擬機(jī)并沒有嚴(yán)格的限制。規(guī)定只需要使用的時(shí)候能夠無歧義定位到目標(biāo)就可以。常量池存在于Class文件中,而Class文件是必須首先通過Java虛擬機(jī)的類加載機(jī)制加載到內(nèi)存中(確切的說是方法區(qū)這個(gè)內(nèi)存區(qū)域,回顧一下,方法區(qū)存放的主要是對象的實(shí)例,這個(gè)Class文件是虛擬機(jī)對外接受訪問的接口)。符號引用屬于常量池中的內(nèi)容,那么是不是說符號引用的目標(biāo)已經(jīng)加載到內(nèi)存中了呢?答案是否定的,因?yàn)?strong>符號引用與虛擬機(jī)的內(nèi)存布局無關(guān),符號引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中了。
直接引用可以是直接指向引用目標(biāo)的指針、相對偏移量或者是一個(gè)能夠間接定位到目標(biāo)的句柄。直接引用是和虛擬機(jī)的內(nèi)存布局有關(guān)的,同一個(gè)符號引用在不同的虛擬機(jī)上翻譯的直接引用一般是不同的。如果有了直接引用,那么引用的目標(biāo)必定是存在內(nèi)存中的。
在常量池中每一項(xiàng)常量都是一個(gè)表,在jdk1.7中共有14中常量類型,所以常量池的項(xiàng)目就對應(yīng)14張表,這14張表的每種類型都不一樣。但是有一個(gè)共同特點(diǎn):表開始的第一位都是一個(gè)u1類型的標(biāo)志位,代表這個(gè)常量屬于哪種類型。
需要注意的是,在Class文件中,方法、字段都需要引用CONSTANT-Utf8_info類型的常量,所以這種類型的常量的長度有一定的限制,也就是Java中方法、字段的最大長度。在CONSTANT-Utf8_info中,其length的值u2,說明Java虛擬機(jī)只能編譯最大大約64KB的變量或者方法名。超過的話將不會進(jìn)行編譯。
常量池之后的數(shù)據(jù)結(jié)構(gòu)是訪問標(biāo)志(access_flags),這個(gè)標(biāo)志主要用于識別一些類或者接口層次的訪問信息,主要包括:這個(gè)Class是類還是接口、是否定義public、是否定義abstract類型;如果是類的話是否被聲明為final等。具體的標(biāo)志訪問如下:
這個(gè)數(shù)據(jù)項(xiàng)主要用于確定這個(gè)類的繼承關(guān)系。
其中類索引和父類索引都是一個(gè)u2類型的數(shù)據(jù),而接口索引集合是一組u2類型的數(shù)據(jù)。在Java中由于不允許多繼承,所以父類索引是唯一的,但是一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,所以得到的接口索引是一個(gè)集合,表示這個(gè)類實(shí)現(xiàn)了哪些接口。
字段表用于描述接口或者類中聲明的變量。
字段包括類級變量和實(shí)例級變量,但是不包括方法內(nèi)部聲明的局部變量(這些變量是存儲在Java虛擬機(jī)棧中的局部變量表中的)。自然,描述一個(gè)字段的信息包括:字段的作用域(public、protected、private)、實(shí)例變量與否(static)、可變性(final)、并發(fā)可見性(volatile)、可否被序列化(transient)、字段數(shù)據(jù)類型(基本數(shù)據(jù)類型、對象、數(shù)組)、字段名稱。字段的信息也被存放在一張表中,其字段表包括三種類型:
u2類型訪問標(biāo)志(access_flags),其訪問標(biāo)志在access_flags中
u2類型的name_index(字段的簡單名稱)
u2類型的描述符(descriptor_index)
上面出現(xiàn)了簡單名稱,上文中出現(xiàn)了全限定名,以及這里出現(xiàn)的描述符,三者有什么區(qū)別呢?其中全限定名稱比較好理解,就是類的完整路徑信息。而簡單名稱則是指沒有類型和參數(shù)修飾的方法或者字段名稱,比如一個(gè)方法如下:
public void inc(int a,int b){ System.out.println(a+b); }
那么這個(gè)方法的簡單名稱就是inc。
相對于以上兩者,描述符相對復(fù)雜一些。描述符的主要的作用是描述字段的數(shù)據(jù)類型、方法的參數(shù)列表和返回值。其中我們熟悉的void,在Class文件中用V表示。下面是完整的描述符標(biāo)志的含義:
對于數(shù)組類型,每一維度使用一個(gè)前置的“[”字符描述,如果是二維數(shù)組,那么就有兩個(gè)“[”符號。比如“java.lang.String[][]”會被記錄成“[[Ljava.lang.String;”
對于方法,則是按照縣參數(shù)列表后返回值的順序進(jìn)行描述的。比如方法int inc(int a,int[] b,char[][] c,int d)的描述符是“(I[I[[CI)I”。
JVM中堆方法表的描述與字段表是一致的,包括了:訪問標(biāo)志、名稱索引、描述符索引、屬性表集合。方法表的結(jié)構(gòu)與字段表是一致的,區(qū)別在于訪問標(biāo)志的不同。在方法中不能用volatile和transient關(guān)鍵字修飾,所以這兩個(gè)標(biāo)志不能用在方法表中。在方法中添加了字段不能使用的訪問標(biāo)志,比如方法可以使用synchronized、native、strictfp、abstract關(guān)鍵字修飾,所以在方法表中就增加了相應(yīng)的訪問標(biāo)志。
要注意的是,如果父類方法沒有在子類中重寫,那么在方法中不會自動出現(xiàn)來自父類的方法信息。同樣的,有可能添加編譯器自動增加的方法,比如方法。
前面的Class文件、字段表和方法表都可以攜帶自己的屬性信息,這個(gè)信息用屬性表進(jìn)行描述,用于描述某些場景專有的信息。在屬性表中沒有類似Class文件的數(shù)據(jù)項(xiàng)目類型和順序的嚴(yán)格要求,只要新的屬性不與現(xiàn)有的屬性名重復(fù),任何人都可以向?qū)傩员碇袑懭胱约憾x的屬性信息。
Code屬性
Java程序方法體中的代碼經(jīng)過javac編譯最終編譯成的字節(jié)碼指令就保存在Code屬性中。但是并非所有的方法表都必須存在這個(gè)屬性。Code屬性是Class文件中最重要的一個(gè)屬性,如果把一個(gè)Java程序中的信息分為代碼(Code)和元數(shù)據(jù)(Metadata,包括類、字段、方法定義及其其他信息)兩部分,那么在整個(gè)Class文件中,Code屬性用于描述代碼,所有其他的數(shù)據(jù)項(xiàng)目都用于描述元數(shù)據(jù)。
Exceptions屬性
這個(gè)屬性的作用是列舉出方法中可能拋出的受查異常(Checked Exception),也就是描述throws 后的列舉的異常
LineNumberTable屬性
主要用于描述Java源代碼行號與字節(jié)碼行號之間的對應(yīng)關(guān)系。這個(gè)屬性也不是必須的。如果沒有這個(gè)屬性,對程序的直接影響就是當(dāng)拋出異常的時(shí)候無法顯示對應(yīng)的行號;并且在調(diào)試的時(shí)候無法通過設(shè)置斷點(diǎn)的方法是調(diào)試程序。
LocalVariableTable屬性
用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量的之間的關(guān)系。也不屬于必須的屬性。如果沒有這個(gè)屬性,產(chǎn)生的直接影響就是當(dāng)別人引用這個(gè)方法的時(shí)候,所有的參數(shù)名稱都會丟失,IDE將會使用諸如args0、args1之類的參數(shù)進(jìn)行顯示。自然,當(dāng)調(diào)試程序的時(shí)候,顯示的參數(shù)名稱是不可知的。
SourceFile屬性
用于記錄這個(gè)Class文件的源碼文件名稱。如果不使用這個(gè)屬性,那么當(dāng)拋出異常的時(shí)候,堆棧中將不會顯示出錯(cuò)代碼所屬的文件名。
ConstantValue屬性
作用是通知虛擬機(jī)自動為靜態(tài)變量賦值。要注意的是,只有被static關(guān)鍵字修飾的額變量才可以使用這個(gè)屬性(類變量)。對于非類變量,初始化是在方法中進(jìn)行的;對于類變量可以選擇兩種方式進(jìn)行變量的初始化:一是在類構(gòu)造器方法中使用;二是是ConstantValue屬性。目前Sun Hotspot的選擇原則是:如果一個(gè)變量同時(shí)使用static和final關(guān)鍵字修飾,并且這個(gè)變量是基本數(shù)據(jù)類型或者java.lang.String類型的話,就使用ConstantValue屬性進(jìn)行初始化。如果沒有被final修飾或者并非是基本數(shù)據(jù)類型,那么將會選擇使用方法進(jìn)行初始化。
InnerClass屬性
這個(gè)屬性主要用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)關(guān)系。
Deprecated以及Synthetic屬性
這兩個(gè)屬性都屬于標(biāo)志類型的布爾屬性,只存在有沒有的區(qū)別。
Deprecated屬性用于表示某個(gè)類、字段或者方法,已經(jīng)被程序作者定為不再推薦使用,可以通過注解@deprecated實(shí)現(xiàn)
Synthetic屬性代表此字段并不是由Java源碼產(chǎn)生的,而是通過編譯器自行添加的。
StackMapTable屬性
該屬性的目的在于代替以前比較消耗性能的基于數(shù)據(jù)流分析的類型推導(dǎo)驗(yàn)證器。
Signature屬性
這個(gè)屬性是專門用來記錄泛型類型的,因?yàn)樵贘ava語言采用的是擦除法實(shí)現(xiàn)的泛型,在字節(jié)碼(Code屬性)中,泛型信息編譯之后會被擦除。擦除法的優(yōu)點(diǎn)是能夠節(jié)省泛型所占的內(nèi)存空間,缺點(diǎn)是在運(yùn)行期間無法通過反射得到泛型信息,而Signature屬性則彌補(bǔ)了這一缺陷?,F(xiàn)在的Java反射API已經(jīng)能夠得到泛型信息,功勞就在于這個(gè)屬性。
BootstrapMethods屬性
這個(gè)屬性用于保存invokedynamic指令引用的引導(dǎo)方法限定符。該指令用于在運(yùn)行時(shí)動態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,并執(zhí)行該方法。
以上是“JVM之Class類文件結(jié)構(gòu)的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
網(wǎng)頁題目:JVM之Class類文件結(jié)構(gòu)的示例分析
本文URL:http://muchs.cn/article0/piocio.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、網(wǎng)站維護(hù)、品牌網(wǎng)站設(shè)計(jì)、品牌網(wǎng)站建設(shè)、網(wǎng)站收錄、面包屑導(dǎo)航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)