深刻分析String.intern和String常量的實現原理

背景

字符串類型在實際應用場景中使用很是頻繁,若是爲每一個字符串常量都生成一個對應的String對象,明顯會形成內存的浪費,針對這一問題,虛擬機實現一個字符串常量池的概念,提供了以下實現:
一、同一個字符串常量,在常量池只有一份副本;
二、經過雙引號聲明的字符串,直接保存在常量池中;
三、若是是String對象,能夠經過String.intern方法,把字符串常量保存到常量池中;java

本文JVM源碼版本 openjdk-7-fcs-src-b147-27數組

疑惑

在不一樣環境執行上述代碼,會獲得不一樣的結果,爲何?
一、JDK1.6的結果:false false
二、JDK1.7的結果:true false數據結構

解惑

其中String.intern在java中是native方法,JDK1.7的註釋以下:jvm

 

一、執行intern方法時,若是常量池中存在和String對象相同的字符串,則返回常量池中對應字符串的引用;
二、若是常量池中不存在對應的字符串,則添加該字符串到常量中,並返回字符串引用;性能

HotSpot1.6實現

常量池的內存在永久代進行分配,永久代和Java堆的內存是物理隔離的,執行intern方法時,若是常量池不存在該字符串,虛擬機會在常量池中複製該字符串,並返回引用,使用intern方法時須要謹慎,避免常量池中字符串過多,致使性能變慢,甚至發生PermGen內存溢出。this

顯然s.intern() == s不可能成立.spa

HotSpot1.7實現

intern方法的HotSpot實現入口位於openjdk\jdk\src\share\native\java\lang\String.c文件中:.net

其中JVM_InternString聲明位於openjdk\hotspot\src\share\vm\prims\jvm.cpp文件中:code

String.intern最終經過StringTable.intern方法實現,其中StringTable是HotSpot字符串常量池的具體實現,1.7的常量池已經在Java堆上分配內存。對象

常量池的初始化

常量池的實現很是簡單,相似JDK中的HashMap,其中StringTable的聲明位於symbolTable.hpp文件中:

 

StringTable最終繼承了BasicHashtable,經過構造方法參數指定常量池的大小StringTableSize,默認爲1009,StringTableSize定義在globals.hpp文件中:

不過在Java7u40版本以後StringTableSize擴大到了60013,能夠經過-XX:StringTableSize = 10009設置StringTable大小,經過-XX:+PrintFlagsFinal打印虛擬機的Global flags參數,能夠得到當前StringTable的大小。

BasicHashtable實現

一、initialize方法初始化常量池的基本值:_table_size、_entry_size等;
二、NEW_C_HEAP_ARRAY方法在堆上分配HashtableBucket;
三、清空StringTable中的HashtableBucket數據;

StringTable.intern實現

一、其中參數string_or_null爲指向原字符串的句柄,name是String對象中字符數組的拷貝、len爲字符數組的長度;
二、java_lang_String::hash_string方法計算出字符串的hash值,實現以下:

三、BasicHashtable.hash_to_index方法計算出該hash值在StringTable中桶的位置index,實現以下:

四、StringTable::lookup方法判斷StringTable指定位置的桶中是否存在相等的字符串,實現以下:

lookup方法經過遍歷HashtableEntry鏈表,若是找到對應的hash值,且字符串值也相等,說明StringTable中已經存在該字符串,則返回該字符串引用,不然返回NULL;
五、若是StringTable不存在該字符串,則經過StringTable::basic_add方法添加字符串引用到StringTable,實現以下:

 

basic_add方法中的條件判斷!string_or_null.is_null()爲true,!JavaObjectsInPerm爲true,因此並不會進行字符串的複製,而是經過HashtableEntry對象封裝原字符串的hash值和指向源字符串的句柄,添加到StringTable對應bucket的鏈表中,並返回指向原字符串句柄;其中變量JavaObjectsInPerm默認爲false,定義以下:

 

經過上述分析:HotSpot1.7實現的常量池在java堆上分配內存,執行intern方法時,若是常量池已經存在相等的字符串,則直接返回字符串引用,不然複製該字符串引用到常量池中並返回;

一、對於變量s1,常量池中不存在"StringTest",因此s1.intern()和 s1都是指向Java堆上的String對象;
二、對於變量s2,常量池中一開始就已經存在"java"字符串,s2.intern()方法返回的是另一個"java"字符串對象,因此s2.intern()和s2指向的並不是同一個對象;

字符串常量如何實現?

相似String s = "hello java"的字符串常量聲明,在HotSpot中是如何實現的呢?

其中字符串常量"hello java"會在編譯過程當中被保存在class文件的Constant pool數據結構中,以下是編譯字節碼實現:

String s = "hello java"對應了兩條字節碼實現:
一、ldc #2
二、astore_1

其中ldc指令的實如今interpreterRuntime.cpp文件中,實現以下:

ldc指令中會根據獲取的常量類型進行不一樣操做,因爲目前是字符串常量,從而調用pool->string_at(index, CHECK)邏輯,實現以下:

其中h_this是指向當前constantPoolOop實例的句柄,最後調用string_at_impl方法:

字符串常量一開始以Symbol類型表示,最終經過StringTable::intern方法生成字符串對象,並把字符串的真實引用更新到constantPool中,這樣下次執行ldc指令時能夠直接返回對象引用。

相關文章
相關標籤/搜索