Java?常量池詳解之字符串常量池實(shí)現(xiàn)代碼
在Java的內(nèi)存分配中,總共3種常量池:
Java 常量池詳解(二)class文件常量池 和 Java 常量池詳解(三)class運(yùn)行時(shí)常量池
1.字符串常量池(String Constant Pool)
在JDK1.7之前運(yùn)行時(shí)常量池邏輯包含字符串常量池存放在方法區(qū), 此時(shí)hotspot虛擬機(jī)對(duì)方法區(qū)的實(shí)現(xiàn)為永久代
在JDK1.7 字符串常量池被從方法區(qū)拿到了堆中, 這里沒(méi)有提到運(yùn)行時(shí)常量池,也就是說(shuō)字符串常量池被單獨(dú)拿到堆,運(yùn)行時(shí)常量池剩下的東西還在方法區(qū), 也就是hotspot中的永久代
在JDK1.8 hotspot移除了永久代用元空間(Metaspace)取而代之, 這時(shí)候字符串常量池還在堆, 運(yùn)行時(shí)常量池還在方法區(qū), 只不過(guò)方法區(qū)的實(shí)現(xiàn)從永久代變成了元空間(Metaspace)
1.1:字符串常量池在Java內(nèi)存區(qū)域的哪個(gè)位置?
在JDK6.0及之前版本,字符串常量池是放在Perm Gen區(qū)(也就是方法區(qū))中;
- 在JDK7.0版本,字符串常量池被移到了堆中了。至于為什么移到堆內(nèi),大概是由于方法區(qū)的內(nèi)存空間太小了。
- (堆內(nèi)是可以進(jìn)行回收的,然后方法區(qū)也是能回收的,但是本身區(qū)域內(nèi)存比較少,如果用的字符串常量太多了,也會(huì)拋java.lang.OutOfMemoryError:PermGenspace 異常)
1.2:字符串常量池是什么?
- 在HotSpot VM里實(shí)現(xiàn)的string pool功能的是一個(gè)StringTable類,它是一個(gè)Hash表,默認(rèn)值大小長(zhǎng)度是1009;這個(gè)StringTable在每個(gè)HotSpot VM的實(shí)例只有一份,被所有的類共享。字符串常量由一個(gè)一個(gè)字符組成,放在了StringTable上。
- 在JDK6.0中,StringTable的長(zhǎng)度是固定的,長(zhǎng)度就是1009,因此如果放入String Pool中的String非常多,就會(huì)造成hash沖突,導(dǎo)致鏈表過(guò)長(zhǎng),當(dāng)調(diào)用String#intern()時(shí)會(huì)需要到鏈表上一個(gè)一個(gè)找,從而導(dǎo)致性能大幅度下降;
- 在JDK7.0中,StringTable的長(zhǎng)度可以通過(guò)參數(shù)指定:
-XX:StringTableSize=66666`
1.3 字符串常量池生成的時(shí)機(jī)?
String a = "a";
全局字符串池里的內(nèi)容是在類加載完成,經(jīng)過(guò)驗(yàn)證,準(zhǔn)備階段之后在堆中生成字符串對(duì)象實(shí)例,然后將該字符串對(duì)象實(shí)例的引用值存到string pool中(記住:string pool中存的是引用值而不是具體的實(shí)例對(duì)象,具體的實(shí)例對(duì)象是在堆中開辟的一塊空間存放的)
如何將String對(duì)象放入到常量池
- “abc” 雙引號(hào)String 對(duì)象會(huì)自動(dòng)放入常量池
- 調(diào)用String的intern 方法也會(huì)將對(duì)象放入到常量池中
String 對(duì)象代碼案例解析
public static void main(String[] args) { String a = "a"; String b = "b"; String c = "a" + "b"; //生成兩個(gè)對(duì)象 一個(gè)"ab" ,一個(gè)新的String 對(duì)象value 值是ab //public String(String original) { // this.value = original.value; // this.hash = original.hash; //} String d = new String("ab"); String e = a + "b"; String f = a + b; String g = "ab"; System.out.println(e == c); System.out.println(c == d); System.out.println(f == c); System.out.println(g == c); String e1 = e.intern(); String c2 = c.intern(); System.out.println(e1 == c2); System.out.println(e1 == c); } //運(yùn)行結(jié)果 false false false true true true
String c =“a” + “b” 和String c = “a” + b (String b= “b”)的區(qū)別
String b = "b"; String c = "a" + "b"; 等價(jià)于 String c ="ab" String c1 = "a" + b; // java 反編譯的結(jié)果 0 ldc #3 <b> //load constant 加載常量 "b" 2 astore_1 // 存入變量1中 3 ldc #4 <ab> //自動(dòng)識(shí)別了 5 astore_2 6 new #7 <java/lang/StringBuilder> 9 dup 10 invokespecial #8 <java/lang/StringBuilder.<init>> 13 ldc #2 <a> 15 invokevirtual #9 <java/lang/StringBuilder.append> 18 aload_1 19 invokevirtual #9 <java/lang/StringBuilder.append> 22 invokevirtual #10 <java/lang/StringBuilder.toString> 25 astore_3 26 return
(1) “a”+“b” 編譯器自動(dòng)識(shí)別了變成了 “ab” => 3 ldc #4
(2) “a” + b(變量)
- 先new 了StringBuilder 對(duì)象,并初始化init
- 然后bulider.append(“a”)
- 從變量1(b)中取出值"b"
- 然后執(zhí)行了bulider.append(“b”)
- 最后執(zhí)行了builder.toString() 方法 給變量3( c1)進(jìn)行賦值
new string(“abc”)創(chuàng)建了幾個(gè)對(duì)象
答案:是兩個(gè) ,new string(xxxx)方法,xxxx傳入的是String對(duì)象。說(shuō)明xxxx也是String對(duì)象。
public String(String original) { this.value = original.value; this.hash = original.hash; }
String 是一個(gè)final 類型對(duì)象是不會(huì)變化的,如果發(fā)生變化,說(shuō)明其實(shí)是新的對(duì)象。
public final class String
解析public native String intern() 方法
如果常量池中存在當(dāng)前字符串, 就會(huì)直接返回當(dāng)前字符串. 如果常量池中沒(méi)有此字符串, 會(huì)將此字符串放入常量池中后, 再返回
native實(shí)現(xiàn)代碼:
\openjdk7\jdk\src\share\native\java\lang\String.c
Java_java_lang_String_intern(JNIEnv *env, jobject this) { return JVM_InternString(env, this); }
\openjdk7\hotspot\src\share\vm\prims\jvm.h
JNIEXPORT jstring JNICALL JVM_InternString(JNIEnv *env, jstring str);
\openjdk7\hotspot\src\share\vm\prims\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); //調(diào)用StringTable::intern 方法 oop result = StringTable::intern(string, CHECK_NULL); return (jstring) JNIHandles::make_local(env, result); JVM_END
\openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp
oop StringTable::intern(Handle string_or_null, jchar* name, int len, TRAPS) { //根據(jù)名字找到對(duì)應(yīng)hash下標(biāo) unsigned int hashValue = java_lang_String::hash_string(name, len); int index = the_table()->hash_to_index(hashValue); //順著對(duì)應(yīng)的鏈表查找對(duì)應(yīng)的值 oop string = the_table()->lookup(index, name, len, hashValue); // Found if (string != NULL) return string; // Otherwise, add to symbol to table return the_table()->basic_add(index, string_or_null, name, len, hashValue, CHECK_NULL); }
\openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp
oop StringTable::lookup(int index, jchar* name, int len, unsigned int hash) { for (HashtableEntry<oop>* l = bucket(index); l != NULL; l = l->next()) { if (l->hash() == hash) { if (java_lang_String::equals(l->literal(), name, len)) { return l->literal(); } } } return NULL; }
1.它的大體實(shí)現(xiàn)結(jié)構(gòu)就是:JAVA 使用 jni 調(diào)用c++實(shí)現(xiàn)的StringTable的intern方法。
2.要注意的是,String的String Pool是一個(gè)固定大小的Hashtable,默認(rèn)值大小長(zhǎng)度是1009,如果放進(jìn)String Pool的String非常多,就會(huì)造成Hash沖突嚴(yán)重,從而導(dǎo)致鏈表會(huì)很長(zhǎng),而鏈表長(zhǎng)了后直接會(huì)造成的影響就是當(dāng)調(diào)用String.intern時(shí)性能會(huì)大幅下降。
Interger 包裝類的池化技術(shù)
public final class Integer extends Number implements Comparable<Integer> { @Native public static final int MIN_VALUE = 0x80000000; @Native public static final int MAX_VALUE = 0x7fffffff; //緩存-128到127的值在IntegerCache里面,可以進(jìn)行共享 private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } public static Integer valueOf(int i) { //是不是在-128到127里面,不是的話就生成新的Integer if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } }
Integer 對(duì)象代碼案例解析
public void test(){ Integer i1 = 10; Integer i2 = 10; Integer i3 = new Integer(10);//新對(duì)象 Integer i4 = new Integer(10);//新對(duì)象 Integer i5 = Integer.valueOf(10);//從緩存池里面獲取。 Integer i6 = Integer.valueOf(128); Integer i7 = 128; System.out.println(i1 == i2); // true System.out.println(i2 == i3); // false System.out.println(i3 == i4); // false System.out.println(i1 == i5); // true System.out.println(i6 == i7); // false } //運(yùn)行結(jié)果: true false false true false
為啥Integer i1 =10 跟Integer.valueOf(10) 是相等的?
因?yàn)镮nteger i1 = 10 底層原理是 Integer i1 = Integer.valueof(10)
//Integer i1 =10 反編譯的結(jié)果 0 bipush 10 2 invokestatic #14 <java/lang/Integer.valueOf> //調(diào)用了Integer.valueof方法 5 astore_1
為啥Integer i1 =128 跟Integer.valueOf(128) 是不相等的?
因?yàn)槌^(guò)-128~127 這個(gè)范圍,就不在緩存池里面,不能共享都是新new 出來(lái)的
public static Integer valueOf(int i) { //是不是在-128到127里面,不是的話就生成新的Integer if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
問(wèn)題:包裝類對(duì)象池是不是 JVM 常量池的一種?
- 包裝類的對(duì)象池是池化技術(shù)的應(yīng)用,并非是虛擬機(jī)層面的東西,而是 Java 在類封裝里實(shí)現(xiàn)的,IntegerCache 是 Integer在內(nèi)部維護(hù)的一個(gè)靜態(tài)內(nèi)部類,用于對(duì)象緩存。
- Integer 對(duì)象池在底層實(shí)際上就是一個(gè)變量名為 cache 的數(shù)組,里面包含了 -128 ~ 127 的 Integer 對(duì)象實(shí)例。使用對(duì)象池的方法就是通過(guò) Integer.valueOf() 返回 cache 中的對(duì)象,像 Integer i = 10這種自動(dòng)裝箱實(shí)際上也是調(diào)用 Integer.valueOf() 完成的
- 這和常量池中字面量的保存有很大區(qū)別,Integer 不需要顯示地出現(xiàn)在代碼中才添加到池中,初始化時(shí)它已經(jīng)包含了所有需要緩存的對(duì)象
到此這篇關(guān)于Java 常量池詳解之字符串常量池的文章就介紹到這了,更多相關(guān)java字符串常量池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
從前端Vue到后端Spring Boot接收J(rèn)SON數(shù)據(jù)的正確姿勢(shì)(常見錯(cuò)誤及問(wèn)題)
這篇文章主要介紹了從前端Vue到后端Spring Boot接收J(rèn)SON數(shù)據(jù)的正確姿勢(shì)(常見錯(cuò)誤及問(wèn)題),本文將從前端Vue到后端Spring Boot,詳細(xì)介紹接收J(rèn)SON數(shù)據(jù)的正確姿勢(shì),幫助開發(fā)人員更好地處理JSON數(shù)據(jù),感興趣的朋友一起看看吧2024-02-02java微信企業(yè)號(hào)開發(fā)之發(fā)送消息(文本、圖片、語(yǔ)音)
這篇文章主要為大家詳細(xì)介紹了java微信企業(yè)號(hào)開發(fā)之發(fā)送消息,發(fā)送類型包括文本、圖片、語(yǔ)音,感興趣的小伙伴們可以參考一下2016-06-06關(guān)于SpringMVC請(qǐng)求域?qū)ο蟮臄?shù)據(jù)共享問(wèn)題
這篇文章主要介紹了SpringMVC請(qǐng)求域?qū)ο蟮臄?shù)據(jù)共享問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02利用spring的攔截器自定義緩存的實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了利用spring的攔截器自定義緩存的實(shí)現(xiàn)實(shí)例代碼,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02基于Java方式實(shí)現(xiàn)數(shù)據(jù)同步
這篇文章主要為大家詳細(xì)介紹了基于Java方式實(shí)現(xiàn)數(shù)據(jù)同步,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08JavaFX 監(jiān)聽窗口關(guān)閉事件實(shí)例詳解
這篇文章主要介紹了JavaFX 監(jiān)聽窗口關(guān)閉事件實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05Spring Security添加二次認(rèn)證的項(xiàng)目實(shí)踐
在用戶自動(dòng)登錄后,可以通過(guò)對(duì)密碼進(jìn)行二次校驗(yàn)進(jìn)而確保用戶的真實(shí)性,本文就來(lái)介紹一下Spring Security添加二次認(rèn)證的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12使用maven-assembly-plugin如何將system 依賴范圍的jar以class 方式
這篇文章主要介紹了使用maven-assembly-plugin如何將system 依賴范圍的jar以class 方式打包進(jìn) jar包中,本文給大家分享完美解決思路,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06