StringTable是什麼?它和String.intern有什麼關係呢?在字符串對象的建立過程當中,StringTable有起到了什麼做用呢?java
一切的答案都在本文中,快來看看吧。c++
intern是String類中的一個native方法,因此它底層是用c++來實現的。感興趣的同窗能夠去查看下JVM的源碼瞭解更多的內容。數組
這裏咱們主要談一下intern的做用。jvm
intern返回的是這個String所表明的對象,怎麼理解呢?this
String class維護了一個私有的String pool, 這個String pool也叫StringTable,中文名字叫作字符串常量池。編碼
當咱們調用intern方法的時候,若是這個StringTable中已經包含了一個相同的String對象(根據equals(Object)方法來判斷兩個String對象是否相等),那麼將會直接返回保存在這個StringTable中的String。spa
若是StringTable中沒有相同的對象,那麼這個String對象將會被加入StringTable,並返回這個String對象的引用。code
因此,當且僅當 s.equals(t) 的時候s.intern() == t.intern()。對象
咱們知道在類文件被編譯成class文件時,每一個class文件都有一個常量池,常量池中存了些什麼東西呢?blog
字符串常量,類和接口名字,字段名,和其餘一些在class中引用的常量。
看一個很是簡單的java類:
public class SimpleString { public String site="www.flydean.com"; }
而後看一下編譯出來的class文件中的Constant Pool:
Constant pool: #1 = Methodref #2.#3 // java/lang/Object."<init>":()V #2 = Class #4 // java/lang/Object #3 = NameAndType #5:#6 // "<init>":()V #4 = Utf8 java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = String #8 // www.flydean.com #8 = Utf8 www.flydean.com #9 = Fieldref #10.#11 // com/flydean/SimpleString.site:Ljava/lang/String; #10 = Class #12 // com/flydean/SimpleString #11 = NameAndType #13:#14 // site:Ljava/lang/String; #12 = Utf8 com/flydean/SimpleString #13 = Utf8 site #14 = Utf8 Ljava/lang/String; #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 LocalVariableTable #18 = Utf8 this #19 = Utf8 Lcom/flydean/SimpleString; #20 = Utf8 SourceFile #21 = Utf8 SimpleString.java
上面的結果,咱們能夠看到class常量池中的index 7存放了一個字符串,這個字符串的實際內容存放在index 8中,是一個變種的Utf8的編碼。
#7 = String #8 // www.flydean.com #8 = Utf8 www.flydean.com
好了,如今問題來了,class文件中的常量池在運行時須要轉換成爲JVM可以識別的運行時常量池,這個運行時的常量池和StringTable和intern有什麼關係呢?
在java對象的實例化過程當中,全部的字符串字面量都會在實例化的時候自動調用intern方法。
若是是第一次調用,則會建立新的String對象,存放在String Table中,並返回該String對象的引用。
從上面的圖中,咱們也能夠出來String Table中存儲的是一個String對象,它和普通的String對象沒有什麼區別,也分爲對象頭,底層的byte數組引用,int hash值等。
若是你不相信,可使用JOL來進行分析:
log.info("{}", ClassLayout.parseInstance("www.flydean.com".intern()).toPrintable());
看下輸出結果:
INFO com.flydean.StringInternJOL - java.lang.String object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 77 1a 06 00 (01110111 00011010 00000110 00000000) (399991) 12 4 byte[] String.value [119, 119, 119, 46, 102, 108, 121, 100, 101, 97, 110, 46, 99, 111, 109] 16 4 int String.hash 0 20 1 byte String.coder 0 21 1 boolean String.hashIsZero false 22 2 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 2 bytes external = 2 bytes total
有了上面的知識,讓咱們分析一下下面的實際問題吧:
String a =new String(new char[]{'a','b','c'}); String b = a.intern(); System.out.println(a == b); String x =new String("def"); String y = x.intern(); System.out.println(x == y);
兩個很簡單的例子,答案是什麼呢? 答案是true和false。
第一個例子按照上面的原理很好理解,在構建String a的時候,String table中並無」abc「這個字符串實例。因此intern方法會將該對象添加到String table中,並返回該對象的引用。
因此a和b實際上是一個對象,返回true。
那麼第二個例子呢?初始化String的時候,不是也沒有」def「這個字符串嗎?爲何回返回false呢?
還記得咱們上面一個小節分析的嗎?全部的字符串字面量在初始化的時候會默認調用intern方法。
也就是說」def「在初始化的時候,已經調用了一次intern了,這個時候String table中已經有」def「這個String了。
因此x和y是兩個不一樣的對象,返回的是false。
注意,上面的例子是在JDK7+以後運行的,若是你是在JDK6中運行,那麼獲得的結果都是false。
JDK6和JDK7有什麼不一樣呢?
在JDK6中,StringTable是存放在方法區中的,而方法區是放在永久代中的。每次調用intern方法,若是String Table中不存在該String對象,則會將該String對象進行一次拷貝,並返回拷貝後String對象的引用。
由於作了一次拷貝,因此引用的不是同一個對象了。結果爲false。
在JDK7以後,StringTable已經被轉移到了java Heap中了,調用intern方法的時候,StringTable能夠直接將該String對象加入StringTable,從而指向的是同一個對象。
若是頻繁的進行String的複製,其實是很是消耗內存空間的。因此在G1垃圾回收器中,可使用下面的:
-XX:+UseStringDeduplication
來開啓String的去重功能。
咱們還記得String對象的底層結構吧,就是一個byte[]數組,String去重的原理就是讓多個字符串對象底層的byte數組指向同一個地方。從而節省內存。
咱們能夠經過使用:
-XX:+PrintStringTableStatistics
參數來查看StringTable的大小。並經過:
-XX:StringTableSizen=n
來指定StringTable的大小。
本文講了String.intern和String table的關係,若是有什麼錯誤或者遺漏的地方,歡迎你們留言給我!
本文做者:flydean程序那些事本文連接:http://www.flydean.com/jvm-string-intern/
本文來源:flydean的博客
歡迎關注個人公衆號:程序那些事,更多精彩等着您!