淺談Java String.intern()

簡書 占小狼
轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!

String.intern()原理

String.intern()是一個(gè)Native方法,底層調(diào)用C++的 StringTable::intern 方法,源碼注釋:當(dāng)調(diào)用 intern 方法時(shí),如果常量池中已經(jīng)該字符串,則返回池中的字符串;否則將此字符串添加到常量池中,并返回字符串的引用。

package com.ctrip.ttd.whywhy;
class Test {
    public static void main(String args[]) {
        String s1 = new StringBuilder().append("String").append("Test").toString();
        System.out.println(s1.intern() == s1);

        String s2 = new StringBuilder().append("ja").append("va").toString();
        System.out.println(s2.intern() == s2);
    }
}

在 JDK6 和 JDK7 中結(jié)果不一樣:

1、JDK6的執(zhí)行結(jié)果:false false
對于這個(gè)結(jié)果很好理解。在JDK6中,常量池在永久代分配內(nèi)存,永久代和Java堆的內(nèi)存是物理隔離的,執(zhí)行intern方法時(shí),如果常量池不存在該字符串,虛擬機(jī)會(huì)在常量池中復(fù)制該字符串,并返回引用,所以需要謹(jǐn)慎使用intern方法,避免常量池中字符串過多,導(dǎo)致性能變慢,甚至發(fā)生PermGen內(nèi)存溢出。

2、JDK7的執(zhí)行結(jié)果:true false
對于這個(gè)結(jié)果就有點(diǎn)懵了。在JDK7中,常量池已經(jīng)在Java堆上分配內(nèi)存,執(zhí)行intern方法時(shí),如果常量池已經(jīng)存在該字符串,則直接返回字符串引用,否則復(fù)制該字符串對象的引用到常量池中并返回,所以在JDK7中,可以重新考慮使用intern方法,減少String對象所占的內(nèi)存空間。


對于變量s1,常量池中沒有 "StringTest" 字符串,s1.intern() 和 s1都是指向Java對象上的String對象。
對于變量s2,常量池中一開始就已經(jīng)存在 "java" 字符串,所以 s2.intern() 返回常量池中 "java" 字符串的引用。

String.intern()性能

常量池底層使用StringTable數(shù)據(jù)結(jié)構(gòu)保存字符串引用,實(shí)現(xiàn)和HashMap類似,根據(jù)字符串的hashcode定位到對應(yīng)的數(shù)組,遍歷鏈表查找字符串,當(dāng)字符串比較多時(shí),會(huì)降低查詢效率。

在JDK6中,由于常量池在PermGen中,受到內(nèi)存大小的限制,不建議使用該方法。
在JDK7、8中,可以通過-XX:StringTableSize參數(shù)StringTable大小,下面通過幾個(gè)測試用例看看intern方法的性能。

public class StringTest {
    public static void main(String[] args) {
        System.out.println(cost(1000000));
    }

    public static long cost(int num) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            String.valueOf(i).intern();
        }
        return System.currentTimeMillis() - start;
    }
}

執(zhí)行一百萬次intern()方法,不同StringTableSize的耗時(shí)情況如下:
1、-XX:StringTableSize=1009, 平均耗時(shí)23000ms;
2、-XX:StringTableSize=10009, 平均耗時(shí)2200ms;
3、-XX:StringTableSize=100009, 平均耗時(shí)200ms;
4、默認(rèn)情況下,平均耗時(shí)400ms;

在默認(rèn)StringTableSize下,執(zhí)行不同次intern()方法的耗時(shí)情況如下:
1、一萬次,平均耗時(shí)5ms;
2、十萬次,平均耗時(shí)25ms;
3、五十萬次,平均耗時(shí)130ms;
4、一百萬次,平均耗時(shí)400ms;
5、五百萬次,平均耗時(shí)5000ms;
6、一千萬次,平均耗時(shí)15000ms;

從這些測試數(shù)據(jù)可以看出,盡管在Java 7以上對intern()做了細(xì)致的優(yōu)化,但其耗時(shí)仍然很顯著,如果無限制的使用intern()方法,將導(dǎo)致系統(tǒng)性能下降,不過可以將有限值的字符串放入常量池,提高內(nèi)存利用率,所以intern()方法是一把雙刃劍。

END。
我是占小狼。
在魔都艱苦奮斗,白天是上班族,晚上是知識(shí)服務(wù)工作者。
如果讀完覺得有收獲的話,記得關(guān)注和點(diǎn)贊哦。
非要打賞的話,我也是不會(huì)拒絕的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • String.intern()原理String.intern()是一個(gè)Native方法,底層調(diào)用c++的Strin...
    水欣閱讀 501評論 0 1
  • ??需要說明的一點(diǎn)是,這篇文章是以《深入理解Java虛擬機(jī)》第二版這本書為基礎(chǔ)的,這里假設(shè)大家已經(jīng)了解了JVM的運(yùn)...
    Geeks_Liu閱讀 14,277評論 5 44
  • 前言 最近一直在關(guān)注“故障排查”的相關(guān)知識(shí),首先著手的是OOM的異常。OOM異常通常會(huì)有Perm區(qū)的OOM(jav...
    LNAmp閱讀 2,684評論 0 6
  • 字符串操作是最常見的操作。在Java中,往往使用String類來進(jìn)行各種字符串操作。而對于String這個(gè)類,其實(shí)...
    zcliu閱讀 256評論 0 0
  • 我們的消息記錄從我上一次,再上一次的生日開始。 你在QQ上祝我生日快樂,而且還講了一大堆溢美之詞……而我實(shí)在是有點(diǎn)...
    洋洋aaa閱讀 219評論 0 0

友情鏈接更多精彩內(nèi)容