徹底搞清楚class常量池、執行時常量池、字串常量池
常量池-靜態常量池
也叫 class檔案常量池,主要存放編譯期生成的各種字面量(Literal)和符號引用(Symbolic References)。
- 字面量:例如文字字串、fina修飾的常量。
int b = 2;
int c = "abcdefg";
- 符號引用:例如類和介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符
// 第3部分,常量池資訊
Constant pool:
常量池-執行時常量池
- 當類載入到記憶體中後,JVM就會將class常量池中的內容存放到執行時常量池中;執行時常量池裡面儲存的主要是編譯期間生成的字面量、符號引用等等。
- 類載入在連結環節的解析過程,會符號引用轉換成直接引用(靜態連結)。此處得到的直接引用也是放到執行時常量池中的。
- 執行期間可以動態放入新的常量。
常量池-字串常量池
字串常量池,也可以理解成執行時常量池分出來的一部分。類載入到記憶體的時候,字串會存到字串常量池裡面。利用池的概念,避免大量頻繁建立字串。
- JDK6時字串常量池位於執行時常量池,JDK7挪到堆中。
Hotspot8之前,使用持久代實現方法區,由於持久代記憶體不好估算,很容易到值OOM:Perm Gen異常。而元空間是本地記憶體,取決於作業系統分配記憶體。
字串常量池位置變遷
Jdk1.6及之前: 有永久代, 執行時常量池在永久代,執行時常量池包含字串常量池
Jdk1.7:有永久代,但已經逐步“去永久代”,字串常量池從永久代裡的執行時常量池分離到堆裡
Jdk1.8及之後: 無永久代,執行時常量池在元空間,字串常量池裡依然在堆裡
建立字串操作
- 字面量賦值
String s = "lzp";
建立字串物件,存放到字串常量池中。s指向常量池中物件引用。
- new String物件
String c = new String("lzp");
new 新字串物件,會在堆和字串常量池中都建立物件。
- intern方法
String中的intern方法是一個 native 的方法,當呼叫 intern方法時,如果池已經包含一個等於此String物件的字串(用equals(oject)方法確定),則返回池中的字串。否則,返回堆中String物件的引用(jdk1.6是將 堆中的String物件 複製到字串常量池,再返回常量池中的引用)。
String c = new String("lzp");
String d = c.intern();
System.out.println(c == d); // false
c指向堆物件,d指向常量池物件,因此必然不相等。
String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
System.out.println(s1 == s2); // true
// 在 JDK 1.6 下輸出是 false,建立了 6 個物件
JDK7以後會建立2個字串常量池物件“he","llo"
,new 3個堆物件”he","llo","hello"
,字串常量池沒有hello物件引用。調s1的intern方法,hello指向new出來的hello物件。因此JDK7版本建立了5個物件。s1調intern()方法,返回堆中物件引用。
當然,很多部落格中也說字串常量池中儲存的是堆物件的引用,即堆中有5個物件2個he,2個llo,1個hello
。字串常量池底層是hotspot的C++實現的,底層類似一個 HashTable, 儲存的本質上是字串物件的引用。
眾說紛紜,不好確定。但是兩種情況的外在表現是一致的,字串字面量物件在常量池中。
編譯期優化
String a = "awecoder";
String b = "awe" + "coder";
System.out.println(a == b); // true
// 下面的也可以優化
"a" + 1 == "a1"
"a" + 3.4 = "a3.4"
b也是字面量,由於"awe"和"coder"在編譯期已確定,JVM編譯期將其優化為一個字串字面量。
String a = "awecoder";
String b = "awe";
final String finalb = "awe";
System.out.println(a == b + "coder"); // false
System.out.println(a == finalb + "coder"); // true
編譯期確定不了,例如new物件便不能優化。對於連線符"+"周圍是否有變數,能夠優化還是取決於變數是否確定。兩者底層實現不同,一個是編譯期優化成一個字面量,另一個底層使用StringBuilder的append()方法實現(反編譯位元組碼檔案可以觀察到)。