本文閱讀時間4分鐘。
字串物件在JVM中可能有兩個存放的位置:字串常量池或堆記憶體。
-
使用常量字串初始化的字串物件,它的值存放在字串常量池中
-
使用字串構造方法建立的字串物件,它的值存放在堆記憶體中
String提供了一個API——java.lang.String.intern()
,這個API可以手動將一個字串物件的值轉移到字串常量池中。
在1.7之前,字串常量池是在PermGen區域,這個區域的大小是固定的——不能在執行時根據需要擴大,也不能被垃圾收集器回收,因此如果程式中有太多的字串呼叫了intern方法的話,就可能造成OOM。
在1.7以後,字串常量池移到了堆記憶體中,並且可以被垃圾收集器回收,這個改動降低了字串常量池OOM的風險。
String s1 = "javaadu";
String s2 = "javaadu";
String s3 = new String("javaadu");
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
String s4 = s3.intern();
System.out.println(s1 == s4); //true
intern方法的實現底層是一個native方法,在Hotspot JVM裡字串常量池它的邏輯在註釋裡寫得很清楚:如果常量池中有這個字串常量,就直接傳回,否則將該字串物件的值存入常量池,再傳回。
這裡以openjdk 1.8的原始碼為例,跟下intern方法的底層實現,String.java檔案對應的C檔案是String.c:
JNIEXPORT jobject JNICALL
Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
return JVM_InternString(env, this);
}
JVM_InternString這個方法的定義在jvm.h,實現在jvm.cpp中,在JVM中,Java世界和C++世界的連線層就是jvm.h和jvm.cpp這兩檔案。
JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
JVMWrapper("JVM_InternString");
JvmtiVMObjectAllocEventCollector oam;
if (str == NULL) return NULL;
oop string = JNIHandles::resolve_non_null(str);
oop result = StringTable::intern(string, CHECK_NULL);
return (jstring) JNIHandles::make_local(env, result);
JVM_END
可以看出,字串常量池在JVM內部就是一個HashTable,也就是上面程式碼中的StringTable。
根據StringTable::intern
方法跟下去,就可以跟到下麵這段程式碼中,如果找到了就直接傳回found_string,如果沒有找到,就將當前的字串加入到HashTable中,然後再傳回。
總結
在Java應用恰當得使用String.intern()方法有助於節省記憶體空間,但是在使用的時候,也需要註意,因為StringTable的大小是固定的,如果常量池中的字串過多,會影響程式執行效率。
你再主動一點點 我們就有故事了