個人上一篇文章《Java虛擬機原理圖解》 一、class文件基本組織結構中已經提到了class的文件結構,在class文件中的魔數、副版本號、主版本以後,緊接着就是常量池的數據區域了,以下圖用紅線包括的位置:html
知道了常量池的位置後,而後讓咱們來揭祕常量池裏究竟有什麼東西吧~ 前端
常量池的組織很簡單,前端的兩個字節佔有的位置叫作常量池計數器(constant_pool_count),它記錄着常量池的組成元素 常量池項(cp_info) 的個數。緊接着會排列着constant_pool_count-1個常量池項(cp_info)。以下圖所示:java
每一個常量池項(cp_info) 都會對應記錄着class文件中的某中類型的字面量。讓咱們先來了解一下常量池項(cp_info)的結構吧:數組
JVM虛擬機規定了不一樣的tag值和不一樣類型的字面量對應關係以下:jvm
因此根據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。ui
如今讓咱們看一下細化了的常量池的結構會是相似下圖所示的樣子:編碼
Java語言規範規定了 int類型和Float 類型的數據類型佔用 4 個字節的空間。那麼存在於class字節碼文件中的該類型的常量是如何存儲的呢?相應地,在常量池中,將 int和Float類型的常量分別使用CONSTANT_Integer_info和 Constant_float_info表示,他們的結構以下所示:lua
舉例:建下面的類 IntAndFloatTest.java,在這個類中,咱們聲明瞭五個變量,可是取值就兩種int類型的10 和Float類型的11f。spa
[java] view plain copy
- package com.louis.jvm;
- public class IntAndFloatTest {
- private final int a = 10;
- private final int b = 10;
- private float c = 11f;
- private float d = 11f;
- private float e = 11f;
- }
而後用編譯器編譯成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。以下圖所示:
Java語言規範規定了 long 類型和 double類型的數據類型佔用8 個字節的空間。那麼存在於class 字節碼文件中的該類型的常量是如何存儲的呢?相應地,在常量池中,將long和double類型的常量分別使用CONSTANT_Long_info和Constant_Double_info表示,他們的結構以下所示:
舉例:建下面的類 LongAndDoubleTest.java,在這個類中,咱們聲明瞭六個變量,可是取值就兩種Long 類型的-6076574518398440533L 和Double 類型的10.1234567890D。
[java] view plain copy
- 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。以下圖所示:
對於字符串而言,JVM會將字符串類型的字面量以UTF-8 編碼格式存儲到在class字節碼文件中。這麼說可能有點摸不着北,咱們先從直觀的Java源碼中中出現的用雙引號"" 括起來的字符串來看,在編譯器編譯的時候,都會將這些字符串轉換成CONSTANT_String_info結構體,而後放置於常量池中。其結構以下所示:
如上圖所示的結構體,CONSTANT_String_info結構體中的string_index的值指向了CONSTANT_Utf8_info結構體,而字符串的utf-8編碼數據就在這個結構體之中。以下圖所示:
請看一例,定義一個簡單的StringTest.java類,而後在這個類里加一個"JVM原理" 字符串,而後,咱們來看看它在class文件中是怎樣組織的。
[java] view plain copy
- 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結構體中。
JVM會將某個Java 類中全部使用到了的類的徹底限定名 以二進制形式的徹底限定名 封裝成CONSTANT_Class_info結構體中,而後將其放置到常量池裏。CONSTANT_Class_info 的tag值爲 7 。其結構以下:
Tips: 類的徹底限定名和 二進制形式的徹底限定名在某個Java源碼中,咱們會使用不少個類,好比咱們定義了一個 ClassTest的類,並把它放到com.louis.jvm 包下,則 ClassTest類的徹底限定名爲com.louis.jvm.ClassTest,將JVM編譯器將類編譯成class文件後,此徹底限定名在class文件中,是以二進制形式的徹底限定名存儲的,即它會把徹底限定符的"."換成"/" ,即在class文件中存儲的 ClassTest類的徹底限定名稱是"com/louis/jvm/ClassTest"。由於這種形式的徹底限定名是放在了class二進制形式的字節碼文件中,因此就稱之爲 二進制形式的徹底限定名。
舉例,咱們定義一個很簡單的ClassTest類,來看一下常量池是怎麼對類的徹底限定名進行存儲的。
[java] view plain copy
- 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常量池項放置到常量池中。以下圖:
[java] view plain copy上述的Other的類,在JDK將其編譯成class文件時,常量池中並無java.util.Date對應的CONSTANT_Class_info常量池項,爲何呢?
- package com.louis.jvm;
- import java.util.Date;
- public class Other{
- private Date date;
- public Other()
- {
- Date da;
- }
- }
在Other類中雖然定義了Date類型的兩個變量date、da,可是JDK編譯的時候,認爲你只是聲明瞭「Ljava/util/Date」類型的變量,並無實際使用到Ljava/util/Date類。將類信息放置到常量池中的目的,是爲了在後續的代碼中有可能會反覆用到它。很顯然,JDK在編譯Other類的時候,會解析到Date類有沒有用到,發現該類在代碼中就沒有用到過,因此就認爲沒有必要將它的信息放置到常量池中了。
將上述的Other類改寫一下,僅使用new Date(),以下圖所示:
[java] view plain copy
- package com.louis.jvm;
- import java.util.Date;
- public class Other{
- public Other()
- {
- new Date();
- }
- }
這時候使用javap -v Other ,能夠查看到常量池中有表示java/util/Date的常量池項:
總結:
1.對於某個類或接口而言,其自身、父類和繼承或實現的接口的信息會被直接組裝成CONSTANT_Class_info常量池項放置到常量池中;
2. 類中或接口中使用到了其餘的類,只有在類中實際使用到了該類時,該類的信息纔會在常量池中有對應的CONSTANT_Class_info常量池項;
3. 類中或接口中僅僅定義某種類型的變量,JDK只會將變量的類型描述信息以UTF-8字符串組成CONSTANT_Utf8_info常量池項放置到常量池中,上面在類中的private Date date;JDK編譯器只會將表示date的數據類型的「Ljava/util/Date」字符串放置到常量池中。