String
類型的常量池比較特殊。它的主要使用方法有兩種:html
String
對象會直接存儲在常量池中。String
對象,可使用String
提供的intern
方法。intern方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中。/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * <p> * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java™ Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */ public native String intern();
String#intern
方法中能夠看出,這個方法是一個native的方法,但註釋寫的很是明瞭:「若是常量池中存在當前字符串, 就會直接返回當前字符串.。若是常量池中沒有此字符串, 會將此字符串放入常量池中後, 再返回」。java
相信不少 JAVA 程序員都作過相似 String s = new String("abc")
這個語句建立了幾個對象的題目。 這種題目主要就是爲了考察程序員對字符串對象的常量池掌握與否。上述的語句中是建立了2個對象,第一個對象是」abc」字符串存儲在常量池中,第二個對象在JAVA Heap中的String對象。程序員
public static void main(String[] args) { String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); }
打印結果是express
false false
false true
具體爲何稍後再解釋,而後將s3.intern()
語句下調一行,放到String s4 = "11"
後面。將s.intern();
放到String s2 = "1"
後面。dom
public static void main(String[] args) { String s = new String("1"); String s2 = "1"; s.intern(); System.out.println(s == s2); String s3 = new String("1") + new String("1"); String s4 = "11"; s3.intern(); System.out.println(s3 == s4); }
打印結果爲:jvm
false false
false false
注:圖中綠色線條表明 string 對象的內容指向。 黑色線條表明地址指向。this
如上圖所示。首先說一下 jdk6中的狀況,在 jdk6中上述的全部打印都是 false 的,由於 jdk6中的常量池是放在 Perm 區中的,Perm區和正常的 JAVA Heap 區域是徹底分開的。上面說過若是是使用引號聲明的字符串都是會直接在字符串常量池中生成,而 new 出來的 String 對象是放在 JAVA Heap 區域。因此拿一個 JAVA Heap 區域的對象地址和字符串常量池的對象地址進行比較確定是不相同的,即便調用String.intern
方法也是沒有任何關係的。spa
在 jdk6 以及之前的版本中,字符串的常量池是放在堆的Perm區的,Perm區是一個類靜態的區域,主要存儲一些加載類的信息,常量池,方法片斷等內容,默認大小隻有4m,一旦常量池中大量使用 intern 是會直接產生java.lang.OutOfMemoryError:PermGen space
錯誤的。在 jdk7 的版本中,字符串常量池已經從Perm區移到正常的Java Heap區域了。爲何要移動,Perm 區域過小是一個主要緣由(jdk8已經直接取消了Perm區域,新創建了一個元區域)。3d
String s3 = new String("1") + new String("1");
,這句代碼中如今生成了2個對象,是字符串常量池中的「1」 和 JAVA Heap中的 s3引用指向的對象。中間還有2個匿名的new String("1")
咱們不去討論它們。此時s3引用對象內容是」11″,但此時常量池中是沒有 「11」對象的。s3.intern();
這一句代碼,是將 s3中的"11"字符串放入String 常量池中,由於此時常量池中不存在"11"字符串,所以常規作法是跟 jdk6 圖中表示的那樣,在常量池中生成一個"11"的對象,關鍵點是 jdk7 中常量池不在Perm區域了,這塊作了調整。常量池中不須要再存儲一份對象了,能夠直接存儲堆中的引用。這份引用指向s3引用的對象。 也就是說引用地址是相同的。String s4 = "11";
這句代碼中」11″是顯示聲明的,所以會直接去常量池中建立,建立的時候發現已經有這個對象了,此時也就是指向s3引用對象的一個引用。因此s4引用就指向和s3同樣了。所以最後的比較 s3 == s4
是 true。String s = new String("1");
第一句代碼,生成了2個對象。常量池中的「1」 和 JAVA Heap 中的字符串對象。s.intern();
這一句是 s 對象去常量池中尋找後發現 「1」 已經在常量池裏了。String s2 = "1";
這句代碼是生成一個 s2的引用指向常量池中的「1」對象。 結果就是 s 和 s2 的引用地址明顯不一樣。圖中畫的很清晰。s3.intern();
的順序是放在String s4 = "11";
後了。這樣,首先執行String s4 = "11";
聲明 s4 的時候常量池中是不存在「11」對象的,執行完畢後,「11「對象是 s4 聲明產生的新對象。而後再執行s3.intern();
時,常量池中「11」對象已經存在了,所以 s3 和 s4 的引用是不一樣的。s.intern();
,這一句日後放也不會有什麼影響了,由於對象池中在執行第一句代碼String s = new String("1");
的時候已經生成「1」對象了。下邊的s2聲明都是直接從常量池中取地址引用的。 s 和 s2 的引用地址是不會相等的。從上述的例子代碼能夠看出 jdk7 版本對 intern 操做和常量池都作了必定的修改。主要包括2點:code
String#intern
方法時,若是存在堆中的對象,會直接保存對象的引用,而不會從新建立對象。 接下來咱們來看一下一個比較常見的使用String#intern
方法的例子。
static final int MAX = 1000 * 10000; static final String[] arr = new String[MAX]; public static void main(String[] args) throws Exception { Integer[] DB_DATA = new Integer[10]; Random random = new Random(10 * 10000); for (int i = 0; i < DB_DATA.length; i++) { DB_DATA[i] = random.nextInt(); } long t = System.currentTimeMillis(); for (int i = 0; i < MAX; i++) { //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])); arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern(); } System.out.println((System.currentTimeMillis() - t) + "ms"); System.gc(); }
運行的參數是:-Xmx2g -Xms2g -Xmn1500M
上述代碼是一個演示代碼,其中有兩條語句不同,一條是使用 intern,一條是未使用 intern。結果以下圖
2160ms
826ms
經過上述結果,咱們發現不使用 intern 的代碼生成了1000w 個字符串,佔用了大約640m 空間。 使用了 intern 的代碼生成了1345個字符串,佔用總空間 133k 左右。其實經過觀察程序中只是用到了10個字符串,因此準確計算後應該是正好相差100w 倍。雖然例子有些極端,但確實能準確反應出 intern 使用後產生的巨大空間節省。
細心的同窗會發現使用了 intern 方法後時間上有了一些增加。這是由於程序中每次都是用了 new String
後, 而後又進行 intern 操做的耗時時間,這一點若是在內存空間充足的狀況下確實是沒法避免的,但咱們平時使用時,內存空間確定不是無限大的,不使用 intern 佔用空間致使 jvm 垃圾回收的時間是要遠遠大於這點時間的。 畢竟這裏使用了1000w次intern 纔多出來1秒鐘多的時間。