日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM - 深入剖析字符串常量池

發(fā)布時間:2025/3/21 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM - 深入剖析字符串常量池 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 字符串常量池
    • 位置的變更
    • JVM對字符串常量池的優(yōu)化
    • 字符串的常見創(chuàng)建方式 (1.7+)
      • 直接賦值字符串
      • new String()
      • intern
    • 經(jīng)典面試題
      • 下列代碼創(chuàng)建幾個對象
      • 案例
      • 案例
      • 案例
      • 案例
      • 案例
      • 案例
      • 案例
      • 案例

字符串常量池

位置的變更

  • Jdk1.6及之前: JVM存在永久代, 運行時常量池在永久代,運行時常量池包含字符串常量池
  • Jdk1.7:有永久代,但已經(jīng)逐步“去永久代”,字符串常量池從永久代里的運行時常量池分離到堆里
  • Jdk1.8及之后: 無永久代,變成了元空間,運行時常量池在元空間,字符串常量池里依然在堆里

看 1.8 , 瘋狂的intern, 拋出了 heap oom ,由此可以推斷出 1.8中的字符串常量池 是在堆中。

如果是1.6 ,會拋出 OutOfMemoryError: PermGen space ,因為1.6中 字符串常量池在永久代中。


JVM對字符串常量池的優(yōu)化

字符串的分配,和其他對象的分配一樣,同樣也需要耗費時間和空間,作為最基礎(chǔ)的數(shù)據(jù)類型,大量頻繁的常講字符串,極大程度上影響了程序的性能。

JVM為了提高性能和減少內(nèi)存開銷,在實例化字符串常量池的時候進(jìn)行了一些優(yōu)化,主要有一下幾點

1. 為字符串開辟一個字符串常量池,用于緩存相同的字符串,類似于環(huán)村區(qū)域
2. 創(chuàng)建字符串常量時,首先要查詢字符串常量池中是否存在該字符串
3. 如若存在該字符串,返回引用實例不存在時,實例化該字符串并放入緩存池中。


字符串的常見創(chuàng)建方式 (1.7+)

注意JDK的版本 ,這里討論的是1.7+

直接賦值字符串

String s = "artisan"; // s指向常量池中的引用

這種方式創(chuàng)建的字符串對象,只會在常量池中 。

因為有"artisan"這個字面量,創(chuàng)建對象s的時候,JVM會先去常量池中通過 equals(key) 方法,判斷是否有相同的對象

  • 若有,直接返回該對象在常量池中的引用
  • 若沒有,則會在常量池中創(chuàng)建一個新對象,再返回引用


new String()

String s1 = new String("artisan"); // s1指向內(nèi)存中的對象引用

這種方式會保證字符串常量池和堆中都有這個對象,沒有就創(chuàng)建,最后返回堆內(nèi)存中的對象引用。

步驟大致如下

因為有"artisan"這個字面量,所以會先檢查字符串常量池中是否存在字符串"artisan"

  • 不存在,先在字符串常量池里創(chuàng)建一個字符串對象;再去內(nèi)存中創(chuàng)建一個字符串對象"artisan";
  • 存在的話,就直接去堆內(nèi)存中創(chuàng)建一個字符串對象"zhuge";
  • 最后,將內(nèi)存中的引用返回


intern

String s1 = new String("artisan");String s2 = s1.intern();System.out.println(s1 == s2); //false

String中的intern方法是一個 native 的方法,當(dāng)調(diào)用 intern方法時,如果池已經(jīng)包含一個等于此String對象的字符串(用equals(oject)方法確定),則返回池中的字符串, 否則,將intern返回的引用指向當(dāng)前字符串 s1 。

jdk1.6版本需要將 s1 復(fù)制到字符串常量池里


經(jīng)典面試題

下列代碼創(chuàng)建幾個對象

public static void main(String[] args) {String s1 = new String("he") + new String("llo");String s2 = s1.intern();System.out.println(s1 == s2);}

對于JDK1.6 , 字符串常量池位于永久代 。

new String("he") + new String("llo")

毫無疑問,堆內(nèi)存中有兩個,但是JDK


JDK 1.6運行結(jié)果 false



案例

String s0="artisan";String s1="artisan";String s2="arti" + "san";System.out.println( s0==s1 ); //trueSystem.out.println( s0==s2 ); //true

s0和s1中的”artisan”都是字符串常量,它們在編譯期就被確定了,所以s0==s1為true;

而”arti”和”san”也都是字符串常量,當(dāng)一個字 符串由多個字符串常量連接而成時,它自己肯定也是字符串常量,所以s2也同樣在編譯期就被優(yōu)化為一個字符串常量"artisan",所以s2也是常量池中” artisan”的一個引用。

所以我們得出s0==s1==s2


案例

再看看這個

String s = "a" + "b" + "c"; //就等價于String s = "abc"; String a = "a"; String b = "b"; String c = "c"; String s1 = a + b + c;

s1 這個就不一樣了,可以通過觀察其JVM指令碼發(fā)現(xiàn)s1的"+"操作會變成如下操作:

StringBuilder temp = new StringBuilder(); temp.append(a).append(b).append(c); String s = temp.toString();

看看StringBuilder#toString ,其實就是new String , 堆中分配內(nèi)存地址


案例

String s0="artisan";String s1=new String("artisan");String s2="arti" + new String("san");System.out.println( s0==s1 );// falseSystem.out.println( s0==s2 );// falseSystem.out.println( s1==s2 );// false

用new String() 創(chuàng)建的字符串不是常量,不能在編譯期就確定,所以new String() 創(chuàng)建的字符串不放入常量池中,它們有自己的地址空間。

s0還是常量池 中"artisan”的引用,s1因為無法在編譯期確定,所以是運行時創(chuàng)建的新對象”artisan”的引用,s2因為有后半部分 new String(”san”)所以也無法在編譯期確定,所以也是一個新創(chuàng)建對象”artisan”的引用;

明白了這些也就知道為何都是false了


案例

String a = "a1";String b = "a" + 1;System.out.println(a == b); // trueString a = "atrue";String b = "a" + "true";System.out.println(a == b); // trueString a = "a3.4";String b = "a" + 3.4;System.out.println(a == b); // true

JVM對于字符串常量的"+“號連接,將在程序編譯期,JVM就將常量字符串的”+“連接優(yōu)化為連接后的值,拿"a” + 1來說,經(jīng)編譯器優(yōu)化后在class中就已經(jīng)是a1。

在編譯期其字符串常量的值就確定下來,故上面程序最終的結(jié)果都為true。


案例

String a = "ab";String bb = "b";String b = "a" + bb;System.out.println(a == b); // false

JVM對于字符串引用,由于在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即"a" + bb無法被編譯器優(yōu)化,只有在程序運行期來動態(tài)分配并將連接后的新地址賦給b。所以上面程序的結(jié)果也就為false。


案例

String a = "ab";final String bb = "b";String b = "a" + bb;System.out.println(a == b); // true

和示例4中唯一不同的是bb字符串加了final修飾,對于final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節(jié)碼流中。所以此時的"a" + bb和"a" + "b"效果是一樣的。故上面程序的結(jié)果為true。


案例

String a = "ab";final String bb = getBB();String b = "a" + bb;System.out.println(a == b); // falseprivate static String getBB(){return "b";}

JVM對于字符串引用bb,它的值在編譯期無法確定,只有在程序運行期調(diào)用方法后,將方法的返回值和"a"來動態(tài)連接并分配地址為b,故上面 程序的結(jié)果為false。


案例

//字符串常量池:"計算機"和"技術(shù)" 堆內(nèi)存:str1引用的對象"計算機技術(shù)" //堆內(nèi)存中還有個StringBuilder的對象,但是會被gc回收,StringBuilder的toString方法會new String(),這個String才是真正返回的對象引用 String str1 = new StringBuilder("計算機").append("技術(shù)").toString(); //沒有出現(xiàn)"計算機技術(shù)"字面量,所以不會在常量池里生成"計算機技術(shù)"對象System.out.println(str1 == str1.intern()); //true //"計算機技術(shù)" 在池中沒有,但是在heap中存在,則intern時,會直接返回該heap中的引用//字符串常量池:"ja"和"va" 堆內(nèi)存:str1引用的對象"java" //堆內(nèi)存中還有個StringBuilder的對象,但是會被gc回收,StringBuilder的toString方法會new String(),這個String才是真正返回的對象引用String str1 = new StringBuilder("ja").append("va").toString(); //沒有出現(xiàn)"java"字面量,所以不會在常量池里生成"java"對象System.out.println(str1 == str1.intern()); //false //java是關(guān)鍵字,在JVM初始化的相關(guān)類里肯定早就放進(jìn)字符串常量池了String s1 = new String("test");System.out.println(s1 == s1.intern()); //false //"test"作為字面量,放入了池中,而new時s1指向的是heap中新生成的string對象,s1.intern()指向的是"test"字面量之前在池中生成的字符串對象String s2 = new StringBuilder("abc").toString();System.out.println(s2 == s2.intern()); //false //同上

總結(jié)

以上是生活随笔為你收集整理的JVM - 深入剖析字符串常量池的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。