徹底搞清楚class常量池、執行時常量池、字串常量池

Awecoder發表於2022-02-10

徹底搞清楚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()方法實現(反編譯位元組碼檔案可以觀察到)。

相關文章