字符串常量池

207 阅读5分钟

你对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. 字符串常量池:(1个对象)"abcd";
  2. 堆:无
  3. 栈:(1个引用)str
    总共:1个对象+1个引用

String str = new String("abc");对象个数?

分析:若字符串常量池该字符串对象

  1. 字符串常量池:(1个对象)"abc";

  2. 堆:(1个对象)new String("abc")

  3. 栈:(1个引用)str
    总共:2个对象+1个引用

    String str = new String("a" + "b");对象个数?

分析:若字符串常量池该字符串对象

  1. 字符串常量池:(1个对象),"ab";
  2. 堆:(1个对象)new String("ab")
  3. 栈:(1个引用)str
    总共:2个对象+1个引用

    String str = new String("ab") + new String("ab");对象个数?

分析:若字符串常量池该字符串对象

  1. 字符串常量池:(1个对象)"ab";
  2. 堆:(4个对象)new String("ab"),new String("ab"),Stringbuilder,以及字符串
  3. 栈:(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,因为编译器会在需要时自动生成,并在拼接完成后进行垃圾回收。

字符串常量中存储的是编译后的字符串,对于这种拼接的是在运行时生成的,所以并不会在字符串常量池中