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

徹底搞清楚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()方法實現(反編譯字節碼文件可以觀察到)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章