class字节码文件中的常量池结构详解
文章目錄
- 前言
- 方法區
- 常量池基本結構
- JVM 所定義的11種常量
- 常量池元素的復合結構
- 常量池的結束位置
- 常量池元素總數量
- 第一個常量池元素
- 父類常量
- 變量型常量池元素
自己的學習筆記,部分節選自《揭秘java虛擬機》
前言
對于一個class文件,內容有:
以u1、u2、u4、u8分別代表1個字節、2個字節、4個字節、8個字節的無符號數
這里主要說class文件中的常量池:
constant_pool_count
常量池計數器,constant_pool_count的值等于constant_pool表中的成員數加1。constant_pool 表的索引值只有在大于 0 且小于 constant_pool_count 時才會被認為是有效的 ,對于 long 和 double 類型有例外情況。
注意:雖然值為 0 的 constant_pool 索引是無效的,但其他用到常量池的數據結構可以使用索引 0 來表示“不引用任何一個常量池項”的意思。
constant_pool[ ]
常量池,constant_pool 是一種表結構,它包含 Class 文件結構及其子結構中引用的所有字符串常量、類或接口名、字段名和其它常量。常量池中的每一項都具備相同的格式特征——第一個字節作為類型標記用于識別該項是哪種類型的常量,稱為“tagbyte”。常量池的索引范圍是 1 至 constant_pool_count?1。
Jdk1.6及之前:有永久代, 常量池在方法區
Jdk1.7:有永久代,但已經逐步“去永久代”,常量池在堆
Jdk1.8及之后: 無永久代,常量池在元空間
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。字面量比較接近于Java語言層面的常量概念,如文本字符串、被聲明為final的常量值等。而符號引用則屬于編譯原理方面的概念,主要包括下面幾類常量:
·被模塊導出或者開放的包(Package)
·類和接口的全限定名(Fully Qualified Name)
·字段的名稱和描述符(Descriptor)
·方法的名稱和描述符
·方法句柄和方法類型(Method Handle、Method Type、Invoke Dynamic)
·動態調用點和動態常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
方法區
既然提到常量池就順便提一下方法區,方法區主要保存的信息是類的元數據。方法區與堆空間類似,它也是被JVM中所有的線程共享的區域。方法區中最為重要的是類的類型信息、常量池、域信息、方法信息。類型信息包括類的完整名稱、父類的完整名稱、類型修飾符(public/protected/private)和類型的直接接口類表。
常量池基本結構
Java類所對應的常量池主要由常量池數量和常量池數組兩部分組成(如下圖所示),常量池數量緊跟在次版本號的后面,占2字節。常量池數組則緊跟在常量池數量之后。 常量池數組,顧名思義,就是一個類似數組的結構。這個數組固化在字節碼文件中,由多個元素組成。與一般數組概念不同的是,常量池數組中不同的元素的類型、結構都是不同的,長度也是不同的,但是每一種元素的第一個數據都是一個u1類型,該字節是標志位,占1個字節(如圖4.4所示)。JVM解析常量池時,根據這個u1類型來獲取該元素的具體類型。常量池的結構組成如圖所示。
使用結構化的方式來描述常量池數組的編排,可以如下描述: tag1元素內容1 tag2元素內容2 tag3元素內容3 … tagn元素內容n 這里為了描述,在tag與元素內容之間添加了空格,但實際的class 二進制文件中,這些tag與元素內容之間絕沒有任何空格信息,也沒有任何其他的多余信息,tag后面緊跟著元素內容,第一個元素內容結束了,緊接著就是第二個元素的tag信息,由此可見,整個字節碼文件的格式設計都是非常緊湊的。
JVM 所定義的11種常量
常量池元素中的不同元素結構與類型都是不同的,正因如此,JVM只能定義有限的元素類型,并針對有限的類型進行專門解析。JVM一共定義了11種常量,
JVM常量池元素一覽表
常量池元素的復合結構
常量池數組中的每一種元素的內容都是復合數據結構的,下面分別給出JVM所定義的常量池中每一種元素的具體結構。 該表中tag值為1的常量池元素CONSTANT_Utf8_info,其組成結構為3部分,分別是:tag、length和bytes,其中tag和length的長度分別是u1、u2,即分別占1字節和2字節。而bytes則是字符串的具體內容,其長度是length字節。在字節碼文件中,該常量池元素最終所占的字節數是: 1 + 2 + length 其他類型的常量池元素的組成結構類似。
常量池元素結構:
可以看到,類的方法信息、接口和繼承信息、屬性信息都是定義在NamedAndType_Info中的。
常量池的結束位置
相信有不少讀者讀到這里,可能潛意識里會突然蹦出這么一個問題:整個字節碼文件由多個部分構成,常量池數組只是其中一塊,JVM在解析字節碼文件時,一定需要分別讀取各個部分的字節流,其中也包括常量池數組。但是常量池中有部分元素的值是bytes數組,其長度是隨機變化的,那么JVM在解析時,是如何知道整個常量池的信息解析到什么位置結束呢?其實經過分析不難發現,首先,class文件給出了常量池的總數;其次,凡是碰到有bytes數組的常量池元素,class文件在常量池的每一個元素之前都會專門劃分出2字節用于描述該常量池元素內容所占的字節長度,這樣一來,常量池中每一個元素的長度是確定的,而常量池的總數也是確定的,JVM據此便可以從class文件中準確地計算出常量池結構體的末端位置(起始位置不用計算,是定死的,從第9字節開始,前面8字節分別是魔數和版本號)。
常量池元素總數量
前面8字節用于描述魔數和版本號,從第9字節開始的一大段字節流都用于描述常量池數組信息。其中,第9和第10字節用于描述常量池元素的總數量。
第9和第10字節所保存的常量池數組大小是0x33,換算成十進制是51,說明該字節碼文件中一共包含51個常量池元素。JVM規定,不使用第0個元素,因此實際上一共有50個常量池元素(下文在解析源碼時,會看到源碼中的確是從第1個元素開始解析的,而不是從第0個)。
第一個常量池元素
常量池數量之后(即從第11字節開始),就是常量池數組。每一個常量池元素都以tag位標開始,tag位標都只占1字節長度。如圖所示。
第11字節對應的值是7,對照上文所給的常量池11種元素的復合結構可知,tag位標為7代表的是CONSTANT_Class_info,即類或接口的符號引用,這種類型的元素的結構組成如下:
◎ tag位標 占1字節
◎ index 占2字節
既然tag位標已經占據了字節碼文件的第11字節,則接下來的第12和13字節將合起來表示index。如上圖所示,這兩個字節對應的值為2。
從第11字節開始,到第13字節為止,一個常量池元素就被描述完成。緊接著開始描述第2個常量池元素
第一個常量池元素后面緊跟著的是第二個常量池元素。其第一字節是01,表示這是一個UTF8編碼的字符串,其結構是:
◎ tag位,占1字節
◎ length,占2字節
◎ bytes,占length字節
如圖所示,tag位后面的2字節的值是4,表示bytes占4字節,其值為0x54657374,其中每兩字節正好代表一個字符,對應的字符串是Test。
*
父類常量
剛才的第1與第2兩個常量用于描述Java類型信息,接下來的第3與第4兩個常量則用于描述父類信息。由于Test.Java類并沒有顯式繼承任何類,因此編譯后處理成默認繼承,即父類是Java.lang.Object。
父類常量的tag位也是07,其類名是java/lang/Object
下面分別給出第3和第4這兩個常量在文件中的內容。
圖4.9顯示,字符串的length值為0x10,即16,于是其后面的16字節都是bytes。由此可以進一步驗證,字符串常量的結構由tag、length和bytes組成,bytes的長度由length指定。
變量型常量池元素
常量池中前面4個元素主要用于描述Java類自身的名稱和其父類名稱,接下來的字節碼流則開始描述類中的變量信息,這些變量既包括類的成員變量,也包括類變量(即靜態變量)信息。 首先看類成員變量a的信息
圖所選中的8字節,一共包含兩個常量池元素信息,這兩個常量池元素的類型都是字符串,因為其tag位都是1。第一個字符串常量的length是1,其值(即bytes)是0x61,正好對應UTF-8編碼的字符a。第二個字符串常量的length也是1,其值是0x49,正好對應UTF-8編碼的字符I。在JVM規范中,若變量的類型是I,則表示該變量的實際類型是int。這與上文對變量a的定義一致。 接著看類變量si的定義,
所選中的27字節,一共描述了兩個常量池元素,這兩個常量池元素的類型也都是字符串。第一個字符串的length為2,其值是0x7369,對應utf-8編碼的字符串si。第二個字符串的length為0x13,即19,其值是0x4C 6A 61 76 61 2F 6C 61 6E 67 2F 49 6E 74 65 67 65 72 3E,這一串值是ASCII字符,每2個十六進制數對應一個ASCII字符,這些數字連起來就對應一個字符串,所對應的字符串是Ljava/lang/Integer;。 這兩個常量池元素合起來,描述了Test類中的static Integer si這樣的類變量。
總結
以上是生活随笔為你收集整理的class字节码文件中的常量池结构详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 类加载器源码、双亲委派、自定义类加载器详
- 下一篇: git回退到之前版本和合并分支查看当前分