《深入理解Java虛擬機》讀書筆記:Class類文件的結構

来源:https://www.cnblogs.com/fhey/archive/2023/08/15/jvm.html
-Advertisement-
Play Games

Sun公司以及其他虛擬機提供商發佈了許多可以運行在各種不同平臺上的虛擬機,這些虛擬機都可以載入和執行同一種平臺無關的的程式存儲格式——位元組碼(ByteCode),從而實現了程式的“一次編寫,到處運行”。“Class文件”這種特定的二進位文件格式所關聯,Class文件中包含了Java虛擬機指令集和符號... ...


                                    Class類文件的結構

 

  Sun公司以及其他虛擬機提供商發佈了許多可以運行在各種不同平臺上的虛擬機,這些虛擬機都可以載入和執行同一種平臺無關的的程式存儲格式——位元組碼(ByteCode),從而實現了程式的“一次編寫,到處運行”。

 

                Java虛擬機提供的語言無關性

 

  “Class文件”這種特定的二進位文件格式所關聯,Class文件中包含了Java虛擬機指令集和符號表以及若幹其他輔助信息。使用Java編譯器可以把Java代碼編譯為存儲位元組碼的Class文件。

Class類文件的結構:

  根據Java虛擬機規範的規定,Class文件格式採用一種類似於C語言結構體的偽結構來存儲數據,這種偽結構中只有兩種數據類型:無符號數和表,後面的解析都要以這兩種數據類型為基礎,所以這裡要先介紹這兩個概念。無符號數屬於基本的數據類型,以u1、u2、u4、u8來分別代表1個位元組、2個位元組、4個位元組和8個位元組的無符號數,無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字元串值。表是由多個無符號數或者其他表作為數據項構成的複合數據類型,所有表都習慣性地以“_info”結尾。表用於描述有層次關係的複合結構的數據,整個Class文件本質上就是一張表,它由表6-1所示的數據項構成。

 

                               Class文件格式

1、 魔數

  每個Class文件的頭4個位元組稱為魔數(Magic Number),它的唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件。Class文件的魔數的獲得很有“浪漫氣息”,值為:0xCAFEBABE(咖啡寶貝?)。

 

2、Class文件的版本

  緊接著魔數的4個位元組存儲的是Class文件的版本號:第5和第6個位元組是次版本號(Minor Version),第7和第8個位元組是主版本號(Major Version)。Java的版本號是從45開始的,JDK 1.1之後的每個JDK大版本發佈主版本號向上加1(JDK 1.0~1.1使用了45.0~45.3的版本號),高版本的JDK能向下相容以前版本的Class文件,但不能運行以後版本的Class文件,即使文件格式並未發生任何變化,虛擬機也必須拒絕執行超過其版本號的Class文件。

 

Class文件版本號

3、常量池

  緊接著主次版本號之後的是常量池入口,常量池可以理解為Class文件之中的資源倉庫,它是Class文件結構中與其他項目關聯最多的數據類型,也是占用Class文件空間最大的數據項目之一,同時它還是在Class文件中第一個出現的表類型數據項目。

常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。

  字面量比較接近於Java語言層面的常量概念,如文本字元串、聲明為final的常量值等。

  而符號引用則屬於編譯原理方面的概念,包括了下麵三類常量:

(1) 類和介面的全限定名(Fully Qualified Name)

(2) 欄位的名稱和描述符(Descriptor)

(3) 方法的名稱和描述符

 

  常量池中每一項常量都是一個表,在JDK 1.7之前共有11種結構各不相同的表結構數據,在JDK 1.7中為了更好地支持動態語言調用,又額外增加了3種(
CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info,本章不會涉及這3種新增的類型,在第8章介紹位元組碼執行和方法調用時,將會詳細講解)。這14種表都有一個共同的特點,就是表開始的第一位是一個u1類型的標誌位(tag,取值見表6-3中標誌列),代表當前這個常量屬於哪種常量類型。這14種常量類型所代表的具體含義見下表。

 

                                   常量池的項目類型

4 、訪問標誌

  在常量池結束之後,緊接著的兩個位元組代表訪問標誌(access_flags),這個標誌用於識別一些類或者介面層次的訪問信息,包括:這個Class是類還是介面;是否定義為public類型;是否定義為abstract類型;如果是類的話,是否被聲明為final等。具體的標誌位以及標誌的含義見下表。

訪問標誌

5 、類索引、父類索引與介面索引集合

  類索引(this_class)和父類索引(super_class)都是一個u2類型的數據,而介面索引集合(interfaces)是一組u2類型的數據的集合,Class文件中由這三項數據來確定這個類的繼承關係。類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類的全限定名。由於Java語言不允許多重繼承,所以父類索引只有一個,除了java.lang.Object之外,所有的Java類都有父類,因此除了java.lang.Object外,所有Java類的父類索引都不為0。介面索引集合就用來描述這個類實現了哪些介面,這些被實現的介面將按implements語句(如果這個類本身是一個介面,則應當是extends語句)後的介面順序從左到右排列在介面索引集合中。

類索引、父類索引和介面索引集合都按順序排列在訪問標誌之後,類索引和父類索引用兩個u2類型的索引值表示,它們各自指向一個類型為CONSTANT_Class_info的類描述符常量,通過CONSTANT_Class_info類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字元串。圖6-6演示了代碼清單6-1的代碼的類索引查找過程。對於介面索引集合,入口的第一項——u2類型的數據為介面計數器(interfaces_count),表示索引表的容量。如果該類沒有實現任何介面,則該計數器值為0,後面介面的索引表不再占用任何位元組。

 

                      圖6-6 類索引查找全限定名的過程

 

6 、欄位表集合

  欄位表(field_info)用於描述介面或者類中聲明的變數。欄位(field)包括類級變數以及實例級變數,但不包括在方法內部聲明的局部變數。我們可以想一想在Java中描述一個欄位可以包含什麼信息?可以包括的信息有:欄位的作用域(public、private、protected修飾符)、是實例變數還是類變數(static修飾符)、可變性(final)、併發可見性(volatile修飾符,是否強制從主記憶體讀寫)、可否被序列化(transient修飾符)、欄位數據類型(基本類型、對象、數組)、欄位名稱。上述這些信息中,各個修飾符都是布爾值,要麼有某個修飾符,要麼沒有,很適合使用標誌位來表示。而欄位叫什麼名字、欄位被定義為什麼數據類型,這些都是無法固定的,只能引用常量池中的常量來描述。表6-8中列出了欄位表的最終格式。

                                表6-8 欄位表結構

  欄位修飾符放在access_flags項目中,它與類中的access_flags項目是非常類似的,都是一個u2的數據類型,其中可以設置的標誌位和含義見表6-9。

 

                                      表6-9 欄位訪問標誌

 

  很明顯,在實際情況中,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三個標誌最多只能選擇其一,ACC_FINAL、ACC_VOLATILE不能同時選擇。介面之中的欄位必須有ACC_PUBLIC、ACC_STATIC、ACC_FINAL標誌,這些都是由Java本身的語言規則所決定的。

  跟隨access_flags標誌的是兩項索引值:name_index和descriptor_index。它們都是對常量池的引用,分別代表著欄位的簡單名稱以及欄位和方法的描述符。現在需要解釋一下“簡單名稱”、“描述符”以及前面出現過多次的“全限定名”這三種特殊字元串的概念。

全限定名和簡單名稱很好理解,以代碼清單6-1中的代碼為例,“
  org/fenixsoft/clazz/TestClass”是這個類的全限定名,僅僅是把類全名中的“.”替換成了“/”而已,為了使連續的多個全限定名之間不產生混淆,在使用時最後一般會加入一個“;”表示全限定名結束。簡單名稱是指沒有類型和參數修飾的方法或者欄位名稱,這個類中的inc()方法和m欄位的簡單名稱分別是“inc”和“m”。

  相對於全限定名和簡單名稱來說,方法和欄位的描述符就要複雜一些。描述符的作用是用來描述欄位的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。根據描述符規則,基本數據類型(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void類型[插圖]都用一個大寫字元來表示,而對象類型則用字元L加對象的全限定名來表示,詳見表6-10。

                                    表6-10 描述符標識字元含義

  欄位表集合中不會列出從超類或者父介面中繼承而來的欄位,但有可能列出原本Java代碼之中不存在的欄位,譬如在內部類中為了保持對外部類的訪問性,會自動添加指向外部類實例的欄位。另外,在Java語言中欄位是無法重載的,兩個欄位的數據類型、修飾符不管是否相同,都必須使用不一樣的名稱,但是對於位元組碼來講,如果兩個欄位的描述符不一致,那欄位重名就是合法的。

 

7、 方法表集合

  方法表的結構如同欄位表一樣,依次包括了訪問標誌(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項,見表6-11。這些數據項目的含義也非常類似,僅在訪問標誌和屬性表集合的可選項中有所區別。

 

                                    表6-11 方法表結構

 

  因為volatile關鍵字和transient關鍵字不能修飾方法,所以方法表的訪問標誌中沒有了ACC_VOLATILE標誌和ACC_TRANSIENT標誌。與之相對的,synchronized、native、strictfp和abstract關鍵字可以修飾方法,所以方法表的訪問標誌中增加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT標誌。對於方法表,所有標誌位及其取值可參見表6-12。

 

                                表6-12 方法訪問標誌

 

 

8、屬性表集合

  屬性表(attribute_info)在前面的講解之中已經出現過數次,在Class文件、欄位表、方法表都可以攜帶自己的屬性表集合,以用於描述某些場景專有的信息。

在最新的《Java虛擬機規範(Java SE 7)》版中,預定義屬性已經增加到21項,具體內容見表6-13。下文中將對其中一些屬性中的關鍵常用的部分進行講解。

 

                表6-13 虛擬機規範預定義的屬性

 

  對於每個屬性,它的名稱需要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性值的結構則是完全自定義的,只需要通過一個u4的長度屬性去說明屬性值所占用的位數即可。一個符合規則的屬性表應該滿足表6-14中所定義的結構。

                                    表6-14 屬性表結構

(1)Code屬性

  Java程式方法體中的代碼經過Javac編譯器處理後,最終變為位元組碼指令存儲在Code屬性內。Code屬性出現在方法表的屬性集合之中,但並非所有的方法表都必須存在這個屬性,譬如介面或者抽象類中的方法就不存在Code屬性。

(2)Exceptions屬性

  這裡的Exceptions屬性是在方法表中與Code屬性平級的一項屬性,讀者不要與前面剛剛講解完的異常表產生混淆。Exceptions屬性的作用是列舉出方法中可能拋出的受查異常(Checked Excepitons),也就是方法描述時在throws關鍵字後面列舉的異常。

(3)LineNumberTable屬性

  LineNumberTable屬性用於描述Java源碼行號與位元組碼行號(位元組碼的偏移量)之間的對應關係。它並不是運行時必需的屬性,但預設會生成到Class文件之中,可以在Javac中分別使用-g:none或-g:lines選項來取消或要求生成這項信息。如果選擇不生成LineNumberTable屬性,對程式運行產生的最主要的影響就是當拋出異常時,堆棧中將不會顯示出錯的行號,並且在調試程式的時候,也無法按照源碼行來設置斷點。

(4)LocalVariableTable屬性

  LocalVariableTable屬性用於描述棧幀中局部變數表中的變數與Java源碼中定義的變數之間的關係,它也不是運行時必需的屬性,但預設會生成到Class文件之中,可以在Javac中分別使用-g:none或-g:vars選項來取消或要求生成這項信息。如果沒有生成這項屬性,最大的影響就是當其他人引用這個方法時,所有的參數名稱都將會丟失,IDE將會使用諸如arg0、arg1之類的占位符代替原有的參數名,這對程式運行沒有影響,但是會對代碼編寫帶來較大不便,而且在調試期間無法根據參數名稱從上下文中獲得參數值。

(5)SourceFile屬性

  SourceFile屬性用於記錄生成這個Class文件的源碼文件名稱。這個屬性也是可選的,可以分別使用Javac的-g:none或-g:source選項來關閉或要求生成這項信息。在Java中,對於大多數的類來說,類名和文件名是一致的,但是有一些特殊情況(如內部類)例外。如果不生成這項屬性,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名。這個屬性是一個定長的屬性

(6)ConstantValue屬性

  ConstantValue屬性的作用是通知虛擬機自動為靜態變數賦值。只有被static關鍵字修飾的變數(類變數)才可以使用這項屬性。

(7)InnerClasses屬性

  InnerClasses屬性用於記錄內部類與宿主類之間的關聯。如果一個類中定義了內部類,那編譯器將會為它以及它所包含的內部類生成InnerClasses屬性。

(8)Deprecated及Synthetic屬性

  Deprecated和Synthetic兩個屬性都屬於標誌類型的布爾屬性,只存在有和沒有的區別,沒有屬性值的概念。Deprecated屬性用於表示某個類、欄位或者方法,已經被程式作者定為不再推薦使用,它可以通過在代碼中使用@deprecated註釋進行設置。Synthetic屬性代表此欄位或者方法並不是由Java源碼直接產生的,而是由編譯器自行添加的,在JDK 1.5之後,標識一個類、欄位或者方法是編譯器自動產生的,也可以設置它們訪問標誌中的ACC_SYNTHETIC標誌位,其中最典型的例子就是Bridge Method。所有由非用戶代碼產生的類、方法及欄位都應當至少設置Synthetic屬性和ACC_SYNTHETIC標誌位中的一項,唯一的例外是實例構造器“<init>”方法和類構造器“<clinit>”方法。

(9)StackMapTable屬性

  StackMapTable屬性在JDK 1.6發佈後增加到了Class文件規範中,它是一個複雜的變長屬性,位於Code屬性的屬性表中。這個屬性會在虛擬機類載入的位元組碼驗證階段被新類型檢查驗證器(Type Checker)使用(見7.3.2節),目的在於代替以前比較消耗性能的基於數據流分析的類型推導驗證器。

(10)Signature屬性

  Signature屬性在JDK 1.5發佈後增加到了Class文件規範之中,它是一個可選的定長屬性,可以出現於類、屬性表和方法表結構的屬性表中。在JDK 1.5中大幅增強了Java語言的語法,在此之後,任何類、介面、初始化方法或成員的泛型簽名如果包含了類型變數(Type Variables)或參數化類型(Parameterized Types),則Signature屬性會為它記錄泛型簽名信息。

  之所以要專門使用這樣一個屬性去記錄泛型類型,是因為Java語言的泛型採用的是擦除法實現的偽泛型,在位元組碼(Code屬性)中,泛型信息編譯(類型變數、參數化類型)之後都通通被擦除掉。使用擦除法的好處是實現簡單(主要修改Javac編譯器,虛擬機內部只做了很少的改動)、非常容易實現Backport,運行期也能夠節省一些類型所占的記憶體空間。但壞處是運行期就無法像C#等有真泛型支持的語言那樣,將泛型類型與用戶定義的普通類型同等對待,例如運行期做反射時無法獲得到泛型信息。Signature屬性就是為了彌補這個缺陷而增設的,現在Java的反射API能夠獲取泛型類型,最終的數據來源也就是這個屬性。

(11)BootstrapMethods屬性

  在JDK 1.7發佈後增加到了Class文件規範之中,它是一個複雜的變長屬性,位於類文件的屬性表中。這個屬性用於保存invokedynamic指令引用的引導方法限定符。《Java虛擬機規範(Java SE 7版)》規定,如果某個類文件結構的常量池中曾經出現過
CONSTANT_InvokeDynamic_info類型的常量,那麼這個類文件的屬性表中必須存在一個明確的BootstrapMethods屬性,另外,即使CONSTANT_InvokeDynamic_info類型的常量在常量池中出現過多次,類文件的屬性表中最多也只能有一個BootstrapMethods屬性。BootstrapMethods屬性與JSR-292中的InvokeDynamic指令和java.lang.Invoke包關係非常密切,要介紹這個屬性的作用,必須先弄清楚InovkeDynamic指令的運作原理,筆者將在第8章專門用1節篇幅去介紹它們,在此先暫時略過。

  目前的Javac暫時無法生成InvokeDynamic指令和BootstrapMethods屬性,必須通過一些非常規的手段才能使用到它們。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 獲取byte中每一位的值 byte byData = 0x36; int n0, n1, n2, n3, n4, n5, n6, n7; n0 = (byData & 0x01) == 0x01 ? 1 : 0; n1 = (byData & 0x02) == 0x02 ? 1 : 0; n2 = ...
  • 背景:在linux環境下,很多服務我們都使用docker來跑,很是方便,容器服務獨立,配置獨立,數據獨立等等,但是有個問題,就是如果某個服務異常了,暫停了,停止了,一直重啟中,我們要怎麼及時的知道是哪個服務,併進行處理,保證業務正常運行。 本文主要介紹使用docker服務自帶的一些命令來實現一個基本 ...
  • Lua程式設計第四版第二部分編程實操自做練習題答案,帶:star:為重點。 ## 14.1 :star: > 該函數用於兩個稀疏矩陣相加 ```lua function martixAdd(a, b) local c = {} for i = 1, #a, 1 do c[i] = {} for k, ...
  • [TOC] ## 類型的基本歸類 **整形家族:** ```c char unsigned char signed char short unsigned short [int] signed short [int] int unsigned int signed int long unsigned ...
  • 在Python中,列表(list)是一種有序、可變的數據結構,用於存儲多個元素。列表可以包含不同類型的元素,包括整數、浮點數、字元串等。實際上列表有點類似C++語言中的數組,但僅僅只是類似,和數組還是有點不一樣的。列表非常適合利用順序和位置定位某一元素,尤其是當元素的順序或內容經常發生改變時。 在P ...
  • # 1. 回顧 > 1. java實現多線程: [1]繼承Thread類並重寫run方法 [2]實現Runnable介面 > > 2. 線程Thread中常用的方法: setName(): Thread.currentThread().getName(): > > ​ static void sle ...
  • [TOC] # 簡介 ImGui 是一個用於C++的用戶界面庫,跨平臺、無依賴,支持OpenGL、DirectX等多種渲染API,是一種即時UI(Immediate Mode User Interface)庫,保留模式與即時模式的區別參考[**保留模式與即時模式**](https://learn.m ...
  • 利用AI幫你讀文章、利用AI幫你分析非結構化數據,這些最為潮流的AI輔助工具,相信很多讀者都在各種媒體上看到過了。但還是有不少人並沒有真正的使用過,這裡有很多原因導致,具體就不細說了,懂的都懂。 今天TJ就給大家推薦一個你可以線上使用,也可以自己搭建的AI輔助工具:[**Quivr**](https ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...