在JVM的(de)內(nei)存模型中,我們(men)經常聽(ting)到常量(liang)池(chi)的(de)概念,我一開(kai)始(shi)時認為常量(liang)池(chi)就是一塊內(nei)存區域,所(suo)(suo)有常量(liang)存放于其中,最近在學習JVM,看到好幾種常量(liang)池(chi)的(de)說(shuo)法(fa),把自己都搞糊涂了。花了一天(tian)時間,查詢各種資料,所(suo)(suo)幸有所(suo)(suo)頓悟,決定(ding)記錄下(xia)來,方便查閱。
注意本文以JVM8-HotSpot為基準(zhun)。
1. 相關知識科普
(1)什么是常量
用(yong)final修飾的(de)成員變(bian)(bian)量(liang)表示(shi)常量(liang),值一旦給定就無法改(gai)變(bian)(bian)!final修飾的(de)變(bian)(bian)量(liang)有三種(zhong):靜態變(bian)(bian)量(liang)(類(lei)變(bian)(bian)量(liang))、實例(li)變(bian)(bian)量(liang)和局部變(bian)(bian)量(liang),分別表示(shi)三種(zhong)類(lei)型的(de)常量(liang)。
(2)為什么使用常(chang)量(liang)池(chi),即(ji)常(chang)量(liang)池(chi)的(de)好(hao)處
常量池是為(wei)了(le)避免頻繁的創(chuang)建和銷毀對(dui)象而(er)影(ying)響系統性能,其實現了(le)對(dui)象的共(gong)享。例如字符(fu)串常量池,在(zai)編譯階段(duan)就把所(suo)有的字符(fu)串字面量放到一(yi)個(ge)常量池中。
-
節省內存空間:常(chang)量池(chi)中所有相(xiang)同的字(zi)符串常(chang)量被合并(bing),只占用一個空間;
-
節(jie)省運行時(shi)間:比較(jiao)字符串時(shi),==比equals()快。對于兩個引(yin)用變量,只用==判斷(duan)引(yin)用是否相等,也就可以判斷(duan)實際值是否相等,new出(chu)來的不行。
(3)==的含義
-
基本數(shu)據類型之間使用==,比較的(de)是(shi)他們的(de)數(shu)值;
-
引用之間應用(yong)==,比較的是(shi)他們在內存中的存放地址。
2. class文件常量池(class constant pool)
public class App {
private String value = "Hello";
public int id = 110;
public final static int CONSTANT = 0x110;
public int getId() {
return id;
}
public void setId(int id) {
int tmp = id + 1;
this.id = tmp;
}
}
使用(yong)javac編譯生成(cheng)App.class,再使用(yong)javap -v App.class 查(cha)看編譯后的(de)內容,這里(li)主要列出常量池(chi)的(de)部分(fen):
Constant pool:
#1 = Methodref #6.#29 // java/lang/Object."<init>":()V
#2 = String #30 // Hello
#3 = Fieldref #5.#31 // com/taylor/test/jvm/App.value:Ljava/lang/String;
#4 = Fieldref #5.#32 // com/taylor/test/jvm/App.id:I
#5 = Class #33 // com/taylor/test/jvm/App
#6 = Class #34 // java/lang/Object
#7 = Utf8 value
#8 = Utf8 Ljava/lang/String;
#9 = Utf8 id
#10 = Utf8 I
#11 = Utf8 CONSTANT
#12 = Utf8 ConstantValue
#13 = Integer 272
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/taylor/test/jvm/App;
#21 = Utf8 getId
#22 = Utf8 ()I
#23 = Utf8 setId
#24 = Utf8 (I)V
#25 = Utf8 tmp
#26 = Utf8 MethodParameters
#27 = Utf8 SourceFile
#28 = Utf8 App.java
#29 = NameAndType #14:#15 // "<init>":()V
#30 = Utf8 Hello
#31 = NameAndType #7:#8 // value:Ljava/lang/String;
#32 = NameAndType #9:#10 // id:I
#33 = Utf8 com/taylor/test/jvm/App
#34 = Utf8 java/lang/Object
class文件常量池主要存放兩大常量:字面量(Literal)和符號引用(Symbolic References)。
(1) 字面(mian)量:接近java語言層(ceng)面(mian)的常量概(gai)念,主要包括:
-
文(wen)本字符串(chuan),也就(jiu)是我們(men)經常(chang)聲明的(de):public String value = "Hello";中的(de)"Hello"
#2 = String #29 // Hello #30 = Utf8 Hello -
用final修飾的靜態變(bian)量
#13 = Integer 272強調一下,字面(mian)(mian)(mian)量指的(de)是(shi)數(shu)據的(de)值,即(ji)”Hello“和0x110(272)。通(tong)過上面(mian)(mian)(mian)對常(chang)量池(chi)的(de)觀察(cha),這兩(liang)個(ge)字面(mian)(mian)(mian)值確實(shi)存在于常(chang)量池(chi)中(zhong)。而對于實(shi)例變量,即(ji)上面(mian)(mian)(mian)的(de)public int id = 110,常(chang)量池(chi)只保(bao)留了他(ta)們的(de)字段描述符(fu)I和字段的(de)名稱id,他(ta)們的(de)字面(mian)(mian)(mian)量不(bu)會存在于常(chang)量池(chi)中(zhong)。小朋友(you),你是(shi)不(bu)是(shi)有很多(duo)問號?110去哪里了?答(da)案是(shi),他(ta)們在對應的(de)字節碼方法里:
第11行對應的 bipush 110public com.taylor.test.jvm.App(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #2 // String Hello 7: putfield #3 // Field value:Ljava/lang/String; 10: aload_0 11: bipush 110 13: putfield #4 // Field id:I 16: return?(2) 符號(hao)引用:主要設(she)涉及(ji)編譯原理(li)方面的概念,包括(kuo)下面三類常量:
-
類和接口的全限定名,將類名中原(yuan)來(lai)的"."替(ti)換為(wei)"/"得(de)到(dao)的,主要用(yong)于在運行時解析得(de)到(dao)類的直接引用(yong)
#5 = Class #27 // com/taylor/test/jvm/App #33 = Utf8 com/taylor/test/jvm/App -
字段(duan)的名稱(cheng)和描述符(字段(duan)類型),字段(duan)也(ye)就是類或者接口中聲明(ming)的變量,包括(kuo)類變量和實例變量
-