天天看點

【Java】字元串常量池(實作原理、垃圾回收)前言一、代碼實驗二、字元串常量池實作原理參考文章

前言

字元串常量池在java6之前位于永久代,永久代從名字上已經告訴我們這裡垃圾回收效果很差,如果字元串常量池中含有大量的字元串,那麼很容易造成永久代溢出。從java7開始,字元串常量池挪到了堆中,堆空間一般比較大,而且堆空間的回收效率很高,是以相對于在永久代,放到堆空間後,記憶體溢出的情況大大減少。

本文接下來先通過代碼觀察一下常量池,然後介紹字元串常量池如何實作的。本文使用的是jdk8。

本文目錄

  • 一、代碼實驗
  • 二、字元串常量池實作原理

一、代碼實驗

這裡主要使用String.intern方法,intern()方法的作用是如果字元串不在常量池中,則将該字元串的引用放入常量池,并将引用傳回,如果在常量池中,則将常量池中字元串的引用傳回。

public static void main(String[] args){
        String s1=new String("hello");[1]
        String s2=s1.intern();[2]
        System.out.println(s1==s2);//false
        System.out.println(s1=="hello");//false
        System.out.println(s2=="hello");//true

        String s3=new StringBuilder().append(s1).append(s2).toString();[3]
        String s4=s3.intern();[4]
        System.out.println(s3==s4);//true
        System.out.println(s3=="hellohello");//true

    }
           

運作結果:

false

false

true

true

true

代碼[1]處因為hello是一個字面常量,是以java會将字元串hello放入常量池一份,同時在堆中建立一個String對象,這兩個對象是不同的,而且字元串常量池中已經有了字元串hello,是以代碼[2]處直接傳回常量池的引用,是以也就出現了下面列印的false、false、true。

代碼[3]處會在堆中建立一個字元串對象hellohello,該字元串此時在常量池中不存在,然後代碼[4]處将堆中的引用放入到常量池中,并傳回該引用,是以s3等于s4。接下來的字面常量hellohello其實就是代碼[4]放入字元串常量池中的引用,是以執行也是true。

下面我寫了一段代碼來驗證字元串常量池挪到了堆區中。

public static void main(String[] args)throws Exception{
        String name = ManagementFactory.getRuntimeMXBean().getName();
        System.out.println(name);
        String pid = name.split("@")[0];
        System.out.println("Pid is:" + pid); //列印PID
        Thread.sleep(2000); //為了更好的觀察現象

        //s1和s2都是很長的字元串
	String s1 = "qqq。。。";
	String s2 = "qqqw。。。。";
	System.out.print(s1.length());

        System.gc();
        s1 = null;
        s2 = null;
        Thread.sleep(2000); //為了更好的觀察現象

        System.gc();
        Thread.sleep(2000); //為了更好的觀察現象

        System.gc();
        Thread.sleep(2000); //為了更好的觀察現象
}
           

下面是運作jstat指令的結果:

PS C:\Program Files\Java\jdk1.8.0_161\bin> .\jstat.exe  -gc 16524 1s
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
13312.0 13312.0  0.0    0.0   82432.0  11578.1   218624.0     0.0     4480.0 781.6  384.0   75.9       0    0.000   0      0.000    0.000
13312.0 13312.0  0.0    0.0   82432.0   1648.7   218624.0    1872.3   4864.0 3635.7 512.0  382.9       1    0.002   1      0.009    0.011
13312.0 13312.0  0.0    0.0   82432.0   1648.7   218624.0    1512.4   4864.0 3641.2 512.0  382.9       2    0.003   2      0.022    0.025
13312.0 13312.0  0.0    0.0   82432.0    0.0     218624.0    1508.7   4864.0 3641.3 512.0  382.9       3    0.004   3      0.042    0.046
           

一共進行了三次垃圾回收,從結果上可以很明顯的看出,老年代(OU)和新生代(EU)的總大小在減少。上面的結果可以說明字元串常量池中的字元串和普通對象一樣,初始位于新生代,随着代數的增加,會被挪到老年代中。

二、字元串常量池實作原理

字元串常量池使用哈希表來存儲字元串,類似于HashMap,初始桶的個數是60013(可以使用-XX:+PrintStringTableStatistics在程式退出時列印結果可以看到桶的個數),如果存儲的字元串非常多,可以使用-XX:StringTableSize增大桶個數,以減少沖突。

參考文章

https://blog.csdn.net/weixin_38308374/article/details/110674739