74

在 Java 中,当一个对象没有实时引用时,它就有资格进行垃圾回收。现在在字符串的情况下,情况并非如此,因为字符串将进入字符串池,而 JVM 将保持对象处于活动状态以供重用。所以这意味着一旦创建的字符串将“永远”不会被垃圾收集?

4

4 回答 4

83

现在如果是字符串,情况并非如此,因为字符串将进入字符串池,而 JVM 将保持对象处于活动状态以供重用。所以这意味着一旦创建的字符串将“永远”不会被垃圾收集?

首先,只有字符串文字(见注释)会被自动插入/添加到字符串池中。 String应用程序在运行时创建的对象不会被实习......除非您的应用程序显式调用String.intern().

其次,实际上在字符串池中垃圾收集对象的规则与其他String对象相同:确实是所有对象。如果 GC 发现它们无法访问,它们将被垃圾回收。

实际上,String对应于字符串文字的对象通常不会成为垃圾回收的候选对象。这是因为在每个使用字面量的方法的代码中都存在对对象的隐式引用。String这意味着String只要方法可以执行,就可以访问。

然而,情况并非总是如此。如果在动态加载的类中定义了字符串文字(例如使用Class.forName(...)),则可以安排该类是unloaded。如果发生这种情况,则String对应于文字的对象可能无法访问,并且最终可能会被 GC 处理。

另请参阅:Java 中何时以及如何收集类垃圾?


笔记:

  1. 字符串文字 ( JLS 3.10.5 ) 是出现在 Java 源代码中的字符串;例如

      "abc"            // string literal
      new String(...)  // not a string literal
    
  2. 通过评估(编译时)常量表达式(JLS 15.28)产生的字符串也可以被实习。

       "abc" + 123      // this is a constant expression
    
  3. 严格来说,并不是所有的 String 字面量都是实习的:

    • 如果 String 文字仅作为常量表达式的子表达式出现在源代码中,则该文字可能不会以任何形式出现在“.class”文件中。这样的文字不会被实习,因为它在运行时不存在。

    • 在 Java 9+ 中,涉及非编译时常量的文字和值的字符串连接可能会有不同的处理方式。现在,在字节码编译器的选项中,字符串连接如下:

       int x = 42;   // not a compile time constant
       String s = "prefix " + x + " suffix";
      

      可能会导致一个字符串常量,如下所示:

       "prefix \1 suffix"
      

      在运行时,上述字符串常量用作生成动态连接方法的“配方”。原始字符串文字(即"prefix "" suffix")不会变成内部字符串对象。

      感谢@Holger指出这一点。更多详细信息在JEP 280和.StringConcatFactory

  4. 在 Java 7 之前,字符串池位于 PermGen 中。对于某些 Java 版本,如果您选择了 CMS 收集器,则默认情况下不会启用 PermGen 的垃圾收集。但是 CMS 从来都不是默认的收集器,并且有一个标志可以通过 CMS 启用 PermGen 收集。(而且没有人应该再为 Java 6 及更早版本开发代码了。)

于 2013-08-23T15:56:32.860 回答
8

你是对的; 实习生池中的字符串永远不会被 GC'd。

但是,大多数字符串都没有被实习。
字符串文字是实习的,传递给的字符串String.intern()是实习的,但所有其他字符串都不是实习的,可以正常进行 GC。

于 2013-08-23T15:36:59.043 回答
2

字符串池中的字符串对象不会被垃圾回收。如果您在程序执行中没有引用它,其他 String 对象将被垃圾收集。

您可能会问哪些字符串对象进入字符串池。字符串池中的对象是:

  • 编译时文字(例如String s1 = "123";

  • 运行时中的 Interned String 对象(例如String s2 = new String("test").intern();

两者都s1引用s2字符串池中的字符串对象。

任何在运行时创建且未实习的对象都将充当普通对象并驻留在堆内存中。这些对象可以被垃圾回收。

这方面的一个例子是:String s3 = s1 + s2;

在这里,s3引用一个与其他对象(不在字符串池中)一起驻留在堆内存中的字符串对象。

于 2019-10-08T17:59:11.850 回答
-1

在 Java 7 之前,字符串池位于永久代空间中。所以字符串字面量从来没有被垃圾回收(这也导致了很多次内存不足的问题)Java 7 之后,字符串池被放置在堆空间中,这是由 JVM 进行垃圾回收的。它还减少了在 JVM 中出现内存不足问题的机会。

于 2021-02-14T04:20:51.360 回答