java8中字符串常量以及GC相應處理機制

[TOC]html

1,常量池

1.1, class文件常量池

​ class文件常量池位於class文件中java

​ class文件頭4個字節稱爲魔數,魔數後面的4個字節爲文件版本號,而版本號以後的就是常量池的入口。該常量池用於存放編譯器生成的各類字面量和符號引用,字面量就是所謂的常量,如字符串,final修飾的常量值等。而符號引用則是用來描述引用目標的,如類和接口的全限定名,方法和字段的名稱和描述符。此時符號引用並不會存儲最終內存佈局信息。git

​ class文件中方法和字段均須要引用CONSTANT_Utf8型常量描述名稱,該類型的最大長度也就是java中方法和字段名的長度,而該類型的長度用2個字節表示,最大值爲65535,因此java中若是定義了超過64kb(2^16*1byte,utf8中一個字符佔一個字節)的英文字符的變量或方法名,將沒法編譯。github

1.2, 運行時常量池

​ 當類或接口建立時,它的class中的常量池會被用來構造運行時常量池,常量池中的符號引用會被解析成具體的內存地址。運行時常量池是jvm方法區的一部分,它能夠在運行時將符號引用解析爲直接引用。bash

​ 運行時常量池位於jvm的元空間中(java8)app

1.3,字符串常量池

​ 字符串常量池底層實現是一個哈希表,能夠經過-XX:StringTableSize參數調整大小。<u>字符串常量池中存儲的是字符串對象的引用,而字符串自己是在堆上分配的(java中的對象基本都在堆上分配)。</u>運行時常量池初始化的時候,字面量的符號引用的初始化會用到字符串常量池。String中的intern方法能夠在運行時將字符串實例加入字符串常量池。jvm

​ 在java1.7之前,字符串常量池是在堆的永久代裏面,大小固定,而從java1.7之後,字符串常量池則移動到java堆中了。佈局

驗證代碼以下:spa

public class test {
    private static String str = "";
    public static void main(String[] args) {
        //-Xms32m -Xmx32m
        char element = 'a';
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 1024*1024*64; i++) {
            sb.append(element);
        }
        str = sb.toString();
        System.out.println();
    }
}

JVM啓動參數:-Xms32m -Xmx32m.net

運行結果以下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	......
	at test.test.main(test.java:17)

能夠發現堆發生溢出(oom),說明常量池在堆中分配。

String.intern()

String類的intern()方法的描述是:String類維護着一個初始化爲空的字符串池,當某String實例調用intern()方法的時候,若是字符串池中已包含與此字符串相同的字符串(用equal(obj)方法判斷相等),則返回池中的字符串對象。不然,將此字符串對象加入到常量池中,並返回此字符串對象的引用。對於任意兩個字符串s和t,當切僅當s.equals(t)爲true,s.intern() ==t.intern()才爲true。<u>全部字面值字符串和字符串賦值常量表達式都使用intern方法進行處理。</u>

示例:

public class InternTest {
    public static void main(String[] args) {
        String s1 = new String("a");
        s1.intern();
        String s2 = "a";
        System.out.println(s1 == s2);

        String s3 = new String("b") + new String("b");
        s3.intern();
        String s4 = "bb";
        System.out.println(s3 == s4);
    }
}

運行結果以下:

false
true

-XX:StringTableSize

能夠經過jvm參數-XX:StringTableSize來配置String常量池的大小,在java6和java7u40以前的版本,其默認大小爲1009,java7u40以及以後的java8中其默認值爲60013。其值要求得是素數。

2,使用new關鍵字和使用字符串字面量的區別

示例:

String str1="abc";
String str2=new String("abc");
  • 當使用String str="abc"這種形式的時候,JVM會在字符串常量池中建立該對象而且str1會指向這個對象;

  • 當使用String str=new String("abc")這種形式的時候,JVM會先檢查在字符串常量池中是否已存在"abc",若是不存在則在常量池中建立該對象,若是已存在則不建立。除此以外,還會在堆外建立一個額外的字符串對象,而且str2會指向這個對象。

驗證代碼:

public class test {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = new String("abc");
        System.out.println(str1);
        System.out.println(str2);
        System.out.println(str1 == str2);
    }
}

運行結果以下:

abc
abc
false

查看該代碼的字節碼,會發現兩種建立字符串對象的方式的不一樣:

L0
    LINENUMBER 9 L0
    LDC "abc" //從常量池加載String
    ASTORE 1
L1
    LINENUMBER 10 L1
    NEW java/lang/String  //建立一個String對象
    DUP  
    LDC "abc"   //從常量池加載String
    INVOKESPECIAL java/lang/String.<init> (Ljava/lang/String;)V //調用構造方法,實例初始化
    ASTORE 2

經過對比發現,使用new關鍵字與直接使用字符串字面量相比,多了建立對象的過程。

3,字符串常量池與GC

先看一個示例:

public class test {
    public static void main(String[] args) {
        System.out.println("---------String with new--------");
        collectWeakReference(new String("dasfsafsafsafsa"));
        System.out.println("---------String with literal--------");
        collectWeakReference("dsafdsafxcdfeghg");
    }
    private static void  collectWeakReference(String obj ) {
        ReferenceQueue<String> rq = new ReferenceQueue<>();
        Reference<String> r = new WeakReference<>(obj, rq);
        obj = null;
        Reference rf;
        int gccount = 10;
        //一次System.gc()並不必定會回收A,因此要多試幾回
        while((rf=rq.poll()) == null && gccount >=0) {
            System.gc();
            gccount--;
        }
        System.out.println(rf);
        if (rf != null) {//若是對象被回收則弱引用會加入引用隊列
            //引用指向的對象已經被回收,存入引入隊列的是弱引用自己,因此這裏最終返回null
            System.out.println(rf.get());
        }
    }
}

運行結果以下:

---------String with new--------
java.lang.ref.WeakReference@5a07e868
null
---------String with literal--------
null

因而可知,指向new關鍵字建立的字符串對象的弱引用會被System.gc()觸發的gc回收掉,而指向字面量字符串的弱引用則不會被System.gc()觸發的gc回收掉。這說明new關鍵字建立的字符串對象若是不可達了會被gc回收,而字符串字面量建立的字符串對象則不會,由於常量池中還持有對該字面量字符串的引用。

參考連接:

https://netsurfingzone.com/core-java/string-constant-pool-in-java

https://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/

http://www.javashuo.com/article/p-klishdbi-mn.html

https://stackoverflow.com/questions/23252767/string-pool-vs-constant-pool

http://java-performance.info/string-intern-in-java-6-7-8/

https://netsurfingzone.com/core-java/string-constant-pool-in-java

http://www.javashuo.com/article/p-dttasbwf-ck.html

https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

https://blog.csdn.net/qq_26929957/article/details/79090406

https://jimmy2angel.github.io/2018/10/26/ConstantPool/

https://droidyue.com/blog/2014/12/21/string-literal-pool-in-java/

http://www.javashuo.com/article/p-ajywqfdk-nt.html

https://blog.omitol.com/2017/09/30/JVM-CP/

相關文章
相關標籤/搜索