你对JDK1.7和JDK1.8中关于运行时常量池和字符串常量池的变化总结得非常准确。下面我对这些概念和变化做进一步详细解释:
1. JDK 1.7及之前版本:
在JDK 1.7之前,HotSpot虚拟机将运行时常量池(Runtime Constant Pool)和字符串常量池(String Constant Pool)都存放在方法区(Method Area)中,而HotSpot对方法区的实现称为永久代(PermGen) 。其中:
- 永久代:用于存放类的元数据、静态变量、常量池(包括字符串常量池)等。
- 字符串常量池:用于存储通过
String.intern()方法生成的字符串常量。
问题:
- 内存限制问题:永久代的大小是固定的,容易在大量加载类或使用大量字符串时导致内存不足,产生
OutOfMemoryError: PermGen space。 - 性能问题:永久代的回收和管理相对复杂,尤其是在动态生成和卸载类的时候,这会影响垃圾回收的效率。
2. JDK 1.7:
在JDK 1.7中,HotSpot对内存管理进行了调整,主要针对字符串常量池的位置进行了改变:
-
字符串常量池(String Constant Pool)移至堆中:在JDK 1.7中,字符串常量池从永久代中移到了堆内存中。堆内存是动态可扩展的,因此解决了字符串常量池的内存不足问题,也提高了字符串处理的效率。
- 这个改变意味着使用
String.intern()方法生成的字符串常量,将存储在堆中,而不是方法区的永久代中。
- 这个改变意味着使用
-
运行时常量池仍保留在方法区中,但依旧是存储类加载时产生的符号引用、字面量等信息。
3. JDK 1.8:
JDK 1.8中,HotSpot VM彻底移除了永久代(PermGen) ,并引入了元空间(Metaspace) ,这一变化极大地优化了内存管理机制:
- 永久代被移除,方法区的实现改为元空间:元空间使用的是本地内存(Native Memory)而不是JVM堆内存。这使得类的元数据和静态变量存储在本地内存中,而不是堆内存中,避免了固定大小内存区域的问题。
- 运行时常量池仍保留在方法区(元空间) :运行时常量池存储类加载时的符号引用和字面量,并且可以在运行时动态扩展。元空间与永久代的实现不同,但作用类似,仍然承担管理运行时常量池的职责。
- 字符串常量池继续留在堆中:在JDK 1.7中已经将字符串常量池从永久代迁移到堆中,JDK 1.8保留了这一设计。因此,字符串常量的管理更加灵活,并且依赖堆内存的垃圾回收机制进行内存回收。
4. 总结:
- JDK 1.7前:运行时常量池和字符串常量池都存放在方法区(即永久代)中。
- JDK 1.7:字符串常量池移至堆中,但运行时常量池仍然保留在方法区(永久代) 。
- JDK 1.8:永久代被移除,方法区用元空间代替,运行时常量池保留在元空间中,字符串常量池继续保存在堆中。
JDK 1.8的这种改动解决了内存限制问题,减少了OutOfMemoryError: PermGen space的发生,并提高了内存管理的灵活性。
5. 创建对象:
String str ="ab" + "cd";对象个数?
分析:若字符串常量池该字符串对象
- 字符串常量池:(1个对象)"abcd";
- 堆:无
- 栈:(1个引用)str
总共:1个对象+1个引用
String str = new String("abc");对象个数?
分析:若字符串常量池该字符串对象
-
字符串常量池:(1个对象)"abc";
-
堆:(1个对象)new String("abc")
-
栈:(1个引用)str
总共:2个对象+1个引用String str = new String("a" + "b");对象个数?
分析:若字符串常量池该字符串对象
- 字符串常量池:(1个对象),"ab";
- 堆:(1个对象)new String("ab")
- 栈:(1个引用)str
总共:2个对象+1个引用String str = new String("ab") + new String("ab");对象个数?
分析:若字符串常量池该字符串对象
- 字符串常量池:(1个对象)"ab";
- 堆:(4个对象)new String("ab"),new String("ab"),Stringbuilder,以及字符串
- 栈:(1个引用)str
总共:3个对象+1个引用 在 Java 中,当你执行如下代码:
String str = new String("ab") + new String("cd");
StringBuilder 对象并没有显式地出现在你的代码中,而是由编译器在字符串拼接操作时隐式创建的。下面是这个过程的详细解析:
StringBuilder sb = new StringBuilder(); // 创建 StringBuilder 对象
sb.append(new String("ab")); // 将 "ab" 添加到 sb 中
sb.append(new String("cd")); // 将 "cd" 添加到 sb 中
String str = sb.toString(); // 从 sb 中生成字符串 "abcd"
-
StringBuilder对象的创建:StringBuilder对象会在堆内存中创建,并用于动态拼接字符串。- 在这个过程中,它不会被引用,除非你显式地保存它(但在这种情况下,你不会直接访问这个
StringBuilder实例)。
-
由于
StringBuilder对象在堆中创建,并在拼接完成后不再使用,所以通常没有引用指向它。在字符串拼接完成后,StringBuilder对象会被垃圾回收器回收。 -
StringBuilder对象是在堆内存中创建的,用于支持字符串的动态拼接。 -
你并不需要显式创建
StringBuilder,因为编译器会在需要时自动生成,并在拼接完成后进行垃圾回收。
字符串常量中存储的是编译后的字符串,对于这种拼接的是在运行时生成的,所以并不会在字符串常量池中