1,常量池
1.1, class文件常量池
class文件常量池位於class文件中
class文件頭4個字節稱為魔數,魔數后面的4個字節為文件版本號,而版本號之后的就是常量池的入口。該常量池用於存放編譯器生成的各種字面量和符號引用,字面量就是所謂的常量,如字符串,final修飾的常量值等。而符號引用則是用來描述引用目標的,如類和接口的全限定名,方法和字段的名稱和描述符。此時符號引用並不會存儲最終內存布局信息。
class文件中方法和字段均需要引用CONSTANT_Utf8
型常量描述名稱,該類型的最大長度也就是java中方法和字段名的長度,而該類型的長度用2個字節表示,最大值為65535,所以java中如果定義了超過64kb(2^16*1byte,utf8中一個字符占一個字節)的英文字符的變量或方法名,將無法編譯。
1.2, 運行時常量池
當類或接口創建時,它的class中的常量池會被用來構造運行時常量池,常量池中的符號引用會被解析成具體的內存地址。運行時常量池是jvm方法區的一部分,它可以在運行時將符號引用解析為直接引用。
運行時常量池位於jvm的元空間中(java8)
1.3,字符串常量池
字符串常量池底層實現是一個哈希表,可以通過-XX:StringTableSize
參數調整大小。字符串常量池中存儲的是字符串對象的引用,而字符串本身是在堆上分配的(java中的對象基本都在堆上分配)。運行時常量池初始化的時候,字面量的符號引用的初始化會用到字符串常量池。String中的intern方法可以在運行時將字符串實例加入字符串常量池。
在java1.7以前,字符串常量池是在堆的永久代里面,大小固定,而從java1.7以后,字符串常量池則移動到java堆中了。
驗證代碼如下:
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
運行結果如下:
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。所有字面值字符串和字符串賦值常量表達式都使用intern方法進行處理。
示例:
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/
https://blog.csdn.net/zm13007310400/article/details/77534349
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
https://www.cnblogs.com/gxyandwmm/p/9495923.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/