在Java的内存分配中,总共3种常量池:
Java 常量池详解(二)class文件常量池 和 Java 常量池详解(三)class运行时常量池
在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
-XX:StringTableSize=66666`
String a = "a";
全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)
public static void main(String[] args) { String a = "a"; String b = "b"; String c = "a" + "b"; //生成两个对象 一个"ab" ,一个新的String 对象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); } //运行结果 false false false true true true
String c =“a” + “b” 和String c = “a” + b (String b= “b”)的区别
String b = "b"; String c = "a" + "b"; 等价于 String c ="ab" String c1 = "a" + b; // java 反编译的结果 0 ldc #3 <b> //load constant 加载常量 "b" 2 astore_1 // 存入变量1中 3 ldc #4 <ab> //自动识别了 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” 编译器自动识别了变成了 “ab” => 3 ldc #4
(2) “a” + b(变量)
答案:是两个 ,new string(xxxx)方法,xxxx传入的是String对象。说明xxxx也是String对象。
public String(String original) { this.value = original.value; this.hash = original.hash; }
String 是一个final 类型对象是不会变化的,如果发生变化,说明其实是新的对象。
public final class String
如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回
native实现代码:
\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); //调用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) { //根据名字找到对应hash下标 unsigned int hashValue = java_lang_String::hash_string(name, len); int index = the_table()->hash_to_index(hashValue); //顺着对应的链表查找对应的值 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.它的大体实现结构就是:JAVA 使用 jni 调用c++实现的StringTable的intern方法。
2.要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。
Interger 包装类的池化技术
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里面,可以进行共享 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); } }
public void test(){ Integer i1 = 10; Integer i2 = 10; Integer i3 = new Integer(10);//新对象 Integer i4 = new Integer(10);//新对象 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 } //运行结果: true false false true false
因为Integer i1 = 10 底层原理是 Integer i1 = Integer.valueof(10)
//Integer i1 =10 反编译的结果 0 bipush 10 2 invokestatic #14 <java/lang/Integer.valueOf> //调用了Integer.valueof方法 5 astore_1
因为超过-128~127 这个范围,就不在缓存池里面,不能共享都是新new 出来的
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); }
问题:包装类对象池是不是 JVM 常量池的一种?