Class檔案中的常量
常量池計數器
常量池是class檔案中非常重要的結構,它描述著整個class檔案的字面量資訊。 常量池是由一組constant_pool結構體陣列組成的,而陣列的大小則由常量池計數器指定。
常量池計數器constant_pool_count 的值 =constant_pool表中的成員數+ 1。
constant_pool表的索引值只有在大於 0 且小於constant_pool_count時才會被認為是有效的。
常量池資料區
常量池,constant_pool是一種表結構,它包含 Class 檔案結構及其子結構中引用的所有字串常量、 類或介面名、欄位名和其它常量。
常量池中的每一項都具備相同的格式特徵——第一個位元組作為型別標記用於識別該項是哪種型別的常量,稱為 “tag byte” 。
常量池的索引範圍是1至constant_pool_count−1。常量池的具體細節我們會稍後討論。
NO1.常量池在class檔案的什麼位置?
NO2.常量池的裡面是怎麼組織的?
常量池的組織很簡單,前端的兩個位元組佔有的位置叫做常量池計數器(constant_pool_count),它記錄著常量池的組成元素常量池項(cp_info)的個數。緊接著會排列著constant_pool_count-1個常量池項(cp_info)。如下圖所示:
NO3.常量池項 (cp_info) 的結構是什麼?
每個常量池項(cp_info) 都會對應記錄著class檔案中的某中型別的字面量。讓我們先來了解一下常量池項(cp_info)
JVM虛擬機器規定了不同的tag值和不同型別的字面量對應關係如下:
所以根據cp_info中的tag 不同的值,可以將cp_info 更細化為以下結構體:
CONSTANT_Utf8_info,CONSTANT_Integer_info,
CONSTANT_Float_info,CONSTANT_Long_info,
CONSTANT_Double_info,CONSTANT_Class_info,
CONSTANT_String_info,CONSTANT_Fieldref_info,
CONSTANT_Methodref_info,CONSTANT_InterfaceMethodref_info,
CONSTANT_NameAndType_info,CONSTANT_MethodHandle_info,
CONSTANT_MethodType_info,CONSTANT_InvokeDynamic_info。
現在讓我們看一下細化了的常量池的結構會是類似下圖所示的樣子:
NO4.常量池能夠表示那些資訊?
NO5. int和float資料型別的常量在常量池中是怎樣表示和儲存的?(CONSTANT_Integer_info, CONSTANT_Float_info)
Java語言規範規定了 int型別和Float 型別的資料型別佔用 4 個位元組的空間。相應地,在常量池中,將 int和Float型別的常量分別使用CONSTANT_Integer_info和 Constant_float_info表示,他們的結構如下所示:
舉例:建下面的類 IntAndFloatTest.java,在這個類中,我們聲明瞭五個變數,但是取值就兩種int型別的10 和Float型別的11f。
1 package com.louis.jvm; 2 3 public class IntAndFloatTest { 4 5 private final int a = 10; 6 private final int b = 10; 7 private float c = 11f; 8 private float d = 11f; 9 private float e = 11f; 10 11 }
然後用編譯器編譯成IntAndFloatTest.class位元組碼檔案,我們通過javap -v IntAndFloatTest 指令來看一下其常量池中的資訊,可以看到雖然我們在程式碼中寫了兩次10 和三次11f,但是常量池中,就只有一個常量10 和一個常量11f,如下圖所示:
從結果上可以看到常量池第#8 個常量池項(cp_info) 就是CONSTANT_Integer_info,值為10;第#23個常量池項(cp_info) 就是CONSTANT_Float_info,值為11f。
程式碼中所有用到 int 型別 10 的地方,會使用指向常量池的指標值#8 定位到第#8 個常量池項(cp_info),即值為 10的結構體CONSTANT_Integer_info。
而用到float型別的11f時,也會指向常量池的指標值#23來定位到第#23個常量池項(cp_info) 即值為11f的結構體CONSTANT_Float_info。如下圖所示:
NO6. long和 double資料型別的常量在常量池中是怎樣表示和儲存的?(CONSTANT_Long_info、CONSTANT_Double_info )
Java語言規範規定了 long 型別和 double型別的資料型別佔用8 個位元組的空間。相應地,在常量池中,將long和double型別的常量分別使用CONSTANT_Long_info和Constant_Double_info表示,他們的結構如下所示:
舉例:建下面的類 LongAndDoubleTest.java,在這個類中,我們聲明瞭六個變數,但是取值就兩種Long 型別的-6076574518398440533L 和Double 型別的10.1234567890D。
package com.louis.jvm; public class LongAndDoubleTest { private long a = -6076574518398440533L; private long b = -6076574518398440533L; private long c = -6076574518398440533L; private double d = 10.1234567890D; private double e = 10.1234567890D; private double f = 10.1234567890D; }
然後用編譯器編譯成 LongAndDoubleTest.class 位元組碼檔案,我們通過javap -v LongAndDoubleTest指令來看一下其常量池中的資訊。
可以看到雖然我們在程式碼中寫了三次-6076574518398440533L 和三次10.1234567890D,但是常量池中,就只有一個常量-6076574518398440533L 和一個常量10.1234567890D,如下圖所示:
從結果上可以看到常量池第 #18 個常量池項(cp_info) 就是CONSTANT_Long_info,值為-6076574518398440533L ;第 #26個常量池項(cp_info) 就是CONSTANT_Double_info,值為10.1234567890D。
程式碼中所有用到 long 型別-6076574518398440533L 的地方,會使用指向常量池的指標值#18 定位到第 #18 個常量池項(cp_info),即值為-6076574518398440533L 的結構體CONSTANT_Long_info。
而用到double型別的10.1234567890D時,也會指向常量池的指標值#26來定位到第 #26 個常量池項(cp_info) 即值為10.1234567890D的結構體CONSTANT_Double_info。如下圖所示:
NO7. String型別的字串常量在常量池中是怎樣表示和儲存的?(CONSTANT_String_info、CONSTANT_Utf8_info)
對於字串而言,JVM會將字串型別的字面量以UTF-8 編碼格式儲存到在class位元組碼檔案中。
我們先從直觀的Java原始碼中中出現的用雙引號"" 括起來的字串來看,在編譯器編譯的時候,都會將這些字串轉換成CONSTANT_String_info結構體,然後放置於常量池中。其結構如下所示:
如上圖所示的結構體,CONSTANT_String_info結構體中的string_index的值指向了CONSTANT_Utf8_info結構體,而字串的utf-8編碼資料就在這個結構體之中。如下圖所示:
請看一例,定義一個簡單的StringTest.java類,然後在這個類里加一個"JVM原理" 字串,然後,我們來看看它在class檔案中是怎樣組織的。
package com.louis.jvm; public class StringTest { private String s1 = "JVM原理"; private String s2 = "JVM原理"; private String s3 = "JVM原理"; private String s4 = "JVM原理"; }
將Java原始碼編譯成StringTest.class檔案後,在此檔案的目錄下執行 javap -v StringTest 命令,會看到如下的常量池資訊的輪廓:
(PS :使用javap -v 指令能看到易於我們閱讀的資訊,檢視真正的位元組碼檔案可以使用HEXWin、NOTEPAD++、UtraEdit 等工具。)
在面的圖中,我們可以看到CONSTANT_String_info結構體位於常量池的第#15個索引位置。而存放"Java虛擬機器原理" 字串的 UTF-8編碼格式的位元組陣列被放到CONSTANT_Utf8_info結構體中,該結構體位於常量池的第#16個索引位置。
上面的圖只是看了個輪廓,讓我們再深入地看一下它們的組織吧。請看下圖:
由上圖可見:“JVM原理”的UTF-8編碼的陣列是:4A564D E5 8E 9FE7 90 86,並且存入了CONSTANT_Utf8_info結構體中。
NO8. 類檔案中定義的類名和類中使用到的類在常量池中是怎樣被組織和儲存的?(CONSTANT_Class_info)
JVM會將某個Java 類中所有使用到了的類的完全限定名 以二進位制形式的完全限定名封裝成CONSTANT_Class_info結構體中,然後將其放置到常量池裡。CONSTANT_Class_info 的tag值為 7 。其結構如下:
(PS:在某個Java原始碼中,我們會使用很多個類,比如我們定義了一個 ClassTest的類,並把它放到com.louis.jvm 包下,則 ClassTest類的完全限定名為com.louis.jvm.ClassTest,將JVM編譯器將類編譯成class檔案後,此完全限定名在class檔案中,是以二進位制形式的完全限定名儲存的,即它會把完全限定符的"."換成"/" ,即在class檔案中儲存的 ClassTest類的完全限定名稱是"com/louis/jvm/ClassTest"。因為這種形式的完全限定名是放在了class二進位制形式的位元組碼檔案中,所以就稱之為 二進位制形式的完全限定名。)
舉例,我們定義一個很簡單的ClassTest類,來看一下常量池是怎麼對類的完全限定名進行儲存的。
package com.jvm; import java.util.Date; public class ClassTest { private Date date =new Date(); }
將Java原始碼編譯成ClassTest.class檔案後,在此檔案的目錄下執行 javap -v ClassTest 命令,會看到如下的常量池資訊的輪廓:
如上圖所示,在ClassTest.class檔案的常量池中,共有 3 個CONSTANT_Class_info結構體,分別表示ClassTest 中用到的Class資訊。
我們就看其中一個表示com/jvm.ClassTest的CONSTANT_Class_info 結構體。它在常量池中的位置是#1,它的name_index值為#2,它指向了常量池的第2 個常量池項,如下所示:
對於某個類而言,其class檔案中至少要有兩個CONSTANT_Class_info常量池項,用來表示自己的類資訊和其父類資訊。
(除了java.lang.Object類除外,其他的任何類都會預設繼承自java.lang.Object)
如果類宣告實現了某些介面,那麼介面的資訊也會生成對應的CONSTANT_Class_info常量池項。
除此之外,如果在類中使用到了其他的類,只有真正使用到了相應的類,JDK編譯器才會將類的資訊組成CONSTANT_Class_info常量池項放置到常量池中。如下圖:
package com.louis.jvm; import java.util.Date; public class Other{ private Date date; public Other() { Date da; } }
上述的Other的類,在JDK將其編譯成class檔案時,常量池中並沒有java.util.Date對應的CONSTANT_Class_info常量池項,為什麼呢?
在Other類中雖然定義了Date型別的兩個變數date、da,但是JDK編譯的時候,認為你只是聲明瞭“Ljava/util/Date”型別的變數,並沒有實際使用到Ljava/util/Date類。
將類資訊放置到常量池中的目的,是為了在後續的程式碼中有可能會反覆用到它。很顯然,JDK在編譯Other類的時候,會解析到Date類有沒有用到,發現該類在程式碼中就沒有用到過,所以就認為沒有必要將它的資訊放置到常量池中了。
將上述的Other類改寫一下,僅使用new Date(),如下圖所示:
package com.louis.jvm; import java.util.Date; public class Other{ public Other() { new Date(); } }
這時候使用javap -v Other ,可以檢視到常量池中有表示java/util/Date的常量池項:
NO9.類中引用到的field欄位在常量池中是怎樣描述的?(CONSTANT_Fieldref_info, CONSTANT_Name_Type_info)
一般而言,我們在定義類的過程中會定義一些 field 欄位,然後會在這個類的其他地方(如方法中)使用到它。有可能我們在類的方法中只使用field欄位一次,也有可能我們會在類定義的方法中使用它很多很多次。
舉一個簡單的例子,我們定一個叫Person的簡單java bean,它有name和age兩個field欄位,如下所示:
package com.louis.jvm; public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
在上面定義的類中,我們在Person類中的一系列方法裡,多次引用到namefield欄位和agefield欄位。
對於JVM編譯器而言,name和age只是一個符號而已,並且它在由於它可能會在此類中重複出現多次,所以JVM把它當作常量來看待,將name和age以field欄位常量的形式儲存到常量池中。
將它name和age封裝成CONSTANT_Fieldref_info常量池項,放到常量池中,在類中引用到它的地方,直接放置一個指向field欄位所在常量池的索引。
上面的Person類,使用javap -v Person指令,檢視class檔案的資訊,你會看到,在Person類中引用到age和namefield欄位的地方,都是指向了常量池中age和namefield欄位對應的常量池項中。表示field欄位的常量池項叫做CONSTANT_Fieldref_info。
怎樣描述某一個field欄位的引用?
例項解析: 現在,讓我們來看一下Person類中定義的namefield欄位在常量池中的表示。通過使用javap -v Person會檢視到如下的常量池資訊:
請讀者看上圖中namefield欄位的資料型別,它在#6個常量池項,以UTF-8編碼格式的字串“Ljava/lang/String;” 表示,這表示著這個field 欄位是Java.lang.String 型別的。
關於field欄位的資料型別,class檔案中儲存的方式和我們在原始碼中宣告的有些不一樣。請看下圖的對應關係:
(PS:如果我們在類中定義了field 欄位,但是沒有在類中的其他地方用到這些欄位,它是不會被編譯器放到常量池中的。讀者可以自己試一下。(當然了,定義了但是沒有在類中的其它地方引用到這種情況很少。))
(PS:只有在類中的其他地方引用到了,才會將他放到常量池中。)
NO10.類中引用到的method方法在常量池中是怎樣描述的?(CONSTANT_Methodref_info, CONSTANT_Name_Type_info)
1.舉例:
還是以Person類為例。在Person類中,我們定義了setName(String name)、getName()、setAge(int age)、getAge()這些方法:
package com.louis.jvm; public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
雖然我們定義了方法,但是這些方法沒有在類總的其他地方被用到(即沒有在類中其他的方法中引用到),所以它們的方法引用資訊並不會放到常量中。
現在我們在類中加一個方法 getInfo(),呼叫了getName()和getAge() 方法:
public String getInfo() { return getName()+"\t"+getAge(); }
這時候JVM編譯器會將getName()和getAge()方法的引用資訊包裝成CONSTANT_Methodref_info結構體放入到常量池之中。
(PS: 這裡的方法呼叫的方式牽涉到Java非常重要的一個術語和機制,叫動態繫結。這個動態繫結問題以後在單獨談談。)
2. 怎樣表示一個方法引用?
3. 方法描述符的組成
4. getName() 方法引用在常量池中的表示
NO11.類中引用到某個介面中定義的method方法在常量池中是怎樣描述的?(CONSTANT_InterfaceMethodref_info,CONSTANT_Name_Type_info)
當我們在某個類中使用到了某個介面中的方法,JVM會將用到的介面中的方法資訊放到這個類的常量池中。
比如我們定義了一個Worker介面,和一個Boss類,在Boss類中呼叫了Worker介面中的方法,這時候在Boss類的常量池中會有Worker介面的方法的引用表示。
1 package com.louis.jvm; 2 3 /** 4 * Worker 介面類 5 * @author luan louis 6 */ 7 public interface Worker{ 8 9 public void work(); 10 11 }
1 package com.louis.jvm; 2 3 /** 4 * Boss 類,makeMoney()方法 呼叫Worker 介面的work 5 * @author louluan 6 */ 7 public class Boss { 8 9 public void makeMoney(Worker worker) 10 { 11 worker.work(); 12 } 13 14 }
我們對Boss.class執行javap -v Boss,然後會看到如下資訊:
如上圖所示,在Boss類的makeMoney()方法中呼叫了Worker介面的work()方法,機器指令是通過invokeinterface指令完成的。
invokeinterface指令後面的運算元,是指向了Boss常量池中Worker介面的work()方法描述,表示的意思就是:“我要呼叫Worker介面的work()方法”。
Worker介面的work()方法引用資訊,JVM會使用CONSTANT_InterfaceMethodref_info結構體來描述,CONSTANT_InterfaceMethodref_info定義如下:
CONSTANT_InterfaceMethodref_info結構體和上面介紹的CONSTANT_Methodref_info 結構體很基本上相同,它們的不同點只有:
1.CONSTANT_InterfaceMethodref_info 的tag 值為11,而CONSTANT_Methodref_info的tag值為10;
2. CONSTANT_InterfaceMethodref_info 描述的是介面中定義的方法,而CONSTANT_Methodref_info描述的是例項類中的方法;
NO12.CONSTANT_MethodType_info,CONSTANT_MethodHandle_info,CONSTANT_InvokeDynamic_info
這三項主要是為了讓Java語言支援動態語言特性而在Java 7 版本中新增的三個常量池項,只會在極其特別的情況能用到它,在class檔案中幾乎不會生成這三個常量池項。
總結:
1.對於某個類或介面而言,其自身、父類和繼承或實現的介面的資訊會被直接組裝成CONSTANT_Class_info常量池項放置到常量池中;
2. 類中或介面中使用到了其他的類,只有在類中實際使用到了該類時,該類的資訊才會在常量池中有對應的CONSTANT_Class_info常量池項;
3. 類中或介面中僅僅定義某種型別的變數,JDK只會將變數的型別描述資訊以UTF-8字串組成CONSTANT_Utf8_info常量池項放置到常量池中,上面在類中的private Date date;JDK編譯器只會將表示date的資料型別的“Ljava/util/Date”字串放置到常量池中。
相關推薦
Class檔案中的常量
常量池計數器 常量池是class檔案中非常重要的結構,它描述著整個class檔案的字面量資訊。 常量池是由一組constant_pool結構體陣列組成的,而陣列的大小則由常量池計數器指定。 常量池計數器constant_pool_count 的值 =constant_pool表中的成員數+ 1。
靜態常量(static final)在class檔案中是怎樣的呢?
最近寫專案遇到一個問題,來回折騰了幾次,終於探究清楚了。不廢話,上例子。 背景:因為專案小,沒有使用配置檔案,所有靜態常量都放在Config.java裡面了 public class Config { public static final String URL
eclipse打斷點只進入class檔案中的解決辦法
內容來源 https://www.cnblogs.com/scode2/p/8671908.html#undefined 是由於對應的Java類跟編譯後的class檔案,沒有關聯上, 解決辦法: 在打斷點除錯的時候,如果發現進入到了class檔案,先彆著急關閉class檔案,在該class檔案
《Java虛擬機器原理圖解》1.3、class檔案中的訪問標誌、類索引、父類索引、介面索引集合
講完了class檔案中的常量池,我們就相當於克服了class檔案中最麻煩的模組了。現在,我們來看一下class檔案中緊接著常量池後面的幾個東西:訪問標誌、類索引、父類索引、介面索引集合 訪問標誌、類索引、父類索引、介面索引集合 在class檔案中的位置
《Java虛擬機器原理圖解》1.5、 class檔案中的方法表集合--method方法在class檔案中是怎樣組織的
0. 前言 瞭解JVM虛擬機器原理是每一個Java程式設計師修煉的必經之路。但是由於JVM虛擬機器中有很多的東西講述的比較寬泛,在當前接觸到的關於JVM虛擬機器原理的教程或者部落格中
JAVA class檔案中的符號引用
在java程式碼中,一個類可能使用另外類或者介面的欄位或者呼叫另外一個類的方法。 在編譯的時候,class檔案中是通過叫做"符號引用"的方式來實現的。 如下面的例子 public interface Intf { public static String s
eclipse打斷點除錯進入到class檔案中,不顯示變數值的解決辦法彙總
問題描述:eclipse打斷點除錯進入到class檔案中,而且監視區不顯示變數結果是由於對應的Java類跟編譯後的class檔案,沒有關聯上,解決辦法:在打斷點除錯的時候,如果發現進入到了class檔案,先彆著急關閉class檔案,在該class檔案中有個按鈕,然後點選這個按鈕,選擇跟該專案對應的專案型別(j
Class檔案中為什麼要使用魔數?
Class檔案是一組以8位位元組為基礎單位的二進位制流,檔案中記錄了一個類或介面的定義資訊,檔案中的各個資料項嚴格按照順序緊湊排列,中間不使用分隔符,所以Class檔案中儲存的都是程式執行必要的引數。 每個Class檔案的頭四個位元組被稱為魔數,使用十六進位制
Java中常量定義在interface和class的區別(轉)
var tac 不能被繼承 ble -o err 模式 variable 個人愛好 最終結論:定義常量在interface和class中其實都行,關鍵是看你的設計和個人愛好。 Java中interface中定義變量默認都是"public static final"類型的,
(一)配置mac環境下的JAVA_HOME 與 (二)配置maven (三)Mac上jdk的配置 (四)在terminal中執行.class檔案
(一)mac環境下,echo $JAVA_HOME 一般輸出為空,但有時候某些構件會需要有javahome的配置,這時就需要把Java home配置好。 步驟: 1, 命令列輸入: /usr/libexec/java_home 我的環境輸出是 /Library/Java/JavaVi
SQL Server資料庫mdf檔案中了勒索病毒class="__cf_email__" data-cfemail="fc9f8e858c889998a39d8f9d9293bc9f939f97">[email p
SQL,資料庫,勒索病毒,mdf檔案中毒,[email protected]_email *SQL Server資料庫mdf檔案中了勒索病毒[email protected]_email。副檔名變為[email protected]_email SQL Serv
Spring中的packagesToScan的方式配置hibernate的class檔案對映規則的理解
Spring中的packagesToScan的方式配置hibernate的class檔案對映規則的理解 1.持久化實體,使用自動掃描class的形式進行配置時,規則如下 (1)&l
修改jar包中class檔案
某日,想要更改jar包中的某個class檔案,有無rar無法解壓jar檔案,故找到如下方式進行操作 1、解壓某個jar包:在需要解壓的jar包目錄下,開啟命令列(cmd),輸入如下命令,輸入:C:\jar>jar xf lm.jar 沒有任何反應就表示解壓成功。 2、反編譯class檔案,相同路徑新
jvm虛擬機器 class檔案常量池與執行時常量池
jvm虛擬機器 class檔案常量池與執行時常量池 class檔案常量池 java檔案編譯後生成class檔案,裡面存有兩部分內容: 類的版本、欄位、方法、介面等描述資訊。(欄位是指我們平時在介面或類裡宣告的各種變數 int a 等) 常量池:存放編譯期生成的字
eclipse中對於jsp檔案訪問之後生成的java和class檔案存在位置
我們一般開發時,使用Tomcat,其在Eclipse中提供了三種位置配置選項: 1.use workspace metadata (使用工作空間元資料) 如果是在這種選擇下,eclipse中jsp檔案,在被訪問之後,就會在eclipse當前工作專案區間下建立相
在Java專案中如何反編譯class檔案及批量反編譯?
前言: 反編譯是一個對目標檔案可執行程式進行逆向分析,從而得到原始碼的過程。尤其是像Java這樣的執行在虛擬機器上的程式語言,更容易進行反編譯得到原始碼。今天介紹幾款反編譯的工具,以及如何更快的批量反編譯。 一、介紹 市面上免費的工具
後端--Java中class檔案結構
最近剛看完《深入理解Java虛擬機器》周志明著 第六章 類檔案結構,在這裡寫一篇關於JVM如何解析Class檔案結構的部落格。 Class類檔案結構 Class檔案是一組以8位元組為基礎單位的二進位制流, 各個資料專案嚴格按照順序緊湊排列在class檔案中, 中間沒有任何分
hta工具:從.css檔案中清理不使用的樣式類(id或class類)
-----------程式碼----------<!DOCTYPE html><html><head><title>清除不使用的樣式</title></head><body><iframe id="html" app
eclipse環境下部署到Tomcat中的專案目錄下缺失class檔案或者不更新
首先要確定project選項裡的Build Automatically勾選上了(自動編譯)。 因為在專案中可能存在一些問題,比如說maven Install 中有錯誤但沒有發現,就會導致eclipse在編譯中出現錯誤而跳過。 在專案的proprites選項中如下圖配置,將Abort buil
[一]class 檔案淺析 .class檔案格式詳解 欄位方法屬性常量池欄位 class檔案屬性表 資料型別 資料結構
ClassFile { u4 magic;//唯一作用是確定這個檔案是否為一個能被虛擬機器所接受的class檔案。魔數值固定為0xCAFEBABE,不會改變 u2 minor_version;//唯一作用是確定這個檔案是否為一個能被虛擬機器所接受的class檔案。魔數值固定為0xCAFEBABE,不會