【问题标题】:Where does Java's String constant pool live, the heap or the stack?Java 的字符串常量池在哪里,堆还是栈?
【发布时间】:2011-06-22 13:10:43
【问题描述】:

我知道常量池和 JVM 用来处理字符串字面量的字符串常量池的概念。但我不知道 JVM 使用哪种类型的内存来存储 String 常量文字。栈还是堆?由于它是一个不与任何实例相关联的文字,我假设它将存储在堆栈中。但是,如果它没有被任何实例引用,则文字必须由 GC 运行收集(如果我错了,请纠正我),那么如果它存储在堆栈中,如何处理呢?

【问题讨论】:

  • 如何将池存储在堆栈上?你知道堆栈的概念吗?
  • 嗨 Scrum Meister,我试图表示它不可能。对不起,错误的约定。关于GC我才知道。谢谢你

标签: java string jvm


【解决方案1】:

从技术上讲,答案都不是。根据 Java 虚拟机规范,存储字符串文字的区域在 runtime constant pool 中。运行时常量池内存区域是基于每个类或每个接口分配的,因此它根本不依赖于任何对象实例。运行时常量池是方法区的子集,它“存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括使用的特殊方法在类和实例初始化和接口类型初始化”。 VM 规范说,虽然 方法区 在逻辑上是堆的一部分,但它并没有规定在方法区中分配的内存要进行垃圾收集或其他与正常数据相关的行为分配给堆的结构。

【讨论】:

  • 实际上,当类在 VM 中加载时,字符串常量将被复制到堆中,复制到 VM 范围的字符串池(在 permgen 中,如 Stephen C 所说),因为相等的字符串不同类中的文字必须是相同的 String 对象(由 JLS)。
  • 谢谢大家的回答。通过这次讨论,我明白了很多。很高兴认识你们:)
  • Paŭlo,这适用于 Sun 的虚拟机,但不一定适用于 JVM 的所有实现。正如 JVM 规范所提到的,虽然运行时常量池和方法区在逻辑上是堆的一部分,但它们不必具有相同的行为。只是语义上的细微差别,真的:)
【解决方案2】:

正如this answer 所解释的,字符串池的确切位置未指定,并且可能因 JVM 实现而异。

有趣的是,在 Java 7 之前,池位于热点 JVM 上堆的 permgen 空间中,但 it has been moved to the main part of the heap since Java 7:

区域:HotSpot
概要:在 JDK 7 中,interned 字符串不再分配在 Java 堆的永久代中,而是 分配在 Java 堆的主要部分(称为年轻代和年老代),以及应用程序创建的其他对象。此更改将导致更多数据驻留在主 Java 堆中,而永久代中的数据更少,因此可能需要调整堆大小。由于此更改,大多数应用程序只会看到相对较小的堆使用差异,但加载许多类或大量使用 String.intern() 方法的大型应用程序将看到更显着的差异。 射频:6962931

而在 Java 8 Hotspot 中,Permanent Generation 已被完全移除。

【讨论】:

    【解决方案3】:

    字符串字面量不存储在堆栈中。绝不。事实上,堆栈中没有存储任何对象。

    字符串字面量(或更准确地说,表示它们的字符串对象)在历史上存储在称为“permgen”堆的堆中。 (Permgen 是永久代的缩写。)

    在正常情况下,字符串字面量和 permgen 堆中的许多其他内容是“永久”可访问的,并且不会被垃圾回收。 (例如,字符串文字始终可以从使用它们的代码对象中访问。)但是,您可以将 JVM 配置为尝试查找和收集不再需要的动态加载的类,这可能会导致字符串文字被垃圾收集.

    澄清 #1 - 我并不是说 Permgen 没有得到 GC'ed。它确实如此,通常是在 JVM 决定运行 Full GC 时。我的观点是,只要使用它们的代码可访问,String literals 就可以访问,只要代码的类加载器可访问,代码就可以访问,对于默认类加载器,这意味着“永远”。

    澄清 #2 - 事实上,Java 7 及更高版本使用常规堆来保存字符串池。因此,表示字符串文字和实习生字符串的字符串对象实际上位于常规堆中。 (有关详细信息,请参阅@assylias 的答案。)


    但我仍在尝试找出字符串文字的存储与使用new 创建的字符串之间的细线。

    没有“细线”。真的很简单:

    • String 表示/对应于字符串文字的对象保存在字符串池中。
    • String::intern 调用创建的String 对象保存在字符串池中。
    • 所有其他 String 对象均未保存在字符串池中。

    然后是字符串池“存储”在哪里的单独问题。在 Java 7 之前,它是 permgen 堆。从 Java 7 开始,它是主堆。

    【讨论】:

      【解决方案4】:

      字符串池

      字符串池(有时也称为字符串规范化)是一种 用相等的值替换几个 String 对象的过程,但 具有单个共享字符串对象的不同身份。你可以实现 这个目标是通过保持你自己的地图(可能是软的 或弱引用,具体取决于您的要求)并使用地图 值作为规范化的值。或者你可以使用 String.intern() 方法 这是JDK提供给你的。

      在 Java 6 中使用 String.intern() 时被许多人禁止 标准,因为如果出现 OutOfMemoryException 的可能性很高 池化失控了。 Oracle Java 7 字符串的实现 池化发生了很大变化。您可以在中查找详细信息 http://bugs.sun.com/view_bug.do?bug_id=6962931http://bugs.sun.com/view_bug.do?bug_id=6962930.

      Java 6 中的 String.intern()

      在那些美好的过去,所有的实习字符串都存储在 PermGen 中 – 堆的固定大小部分,主要用于存储加载的类 和字符串池。除了显式实习字符串,PermGen 字符串 pool 还包含您程序中之前使用的所有文字字符串 (这里使用了重要的词——如果一个类或方法从未被 加载/调用,其中定义的任何常量都不会被加载)。

      Java 6 中这种字符串池的最大问题是它的位置—— 永久代。 PermGen 有一个固定的大小,不能在 运行。您可以使用 -XX:MaxPermSize=96m 选项进行设置。据我所知 知道,默认 PermGen 大小在 32M 和 96M 之间变化,具体取决于 该平台。你可以增加它的大小,但它的大小仍然是 固定的。这种限制需要非常小心地使用 String.intern – 你最好不要使用这种方法来实习任何不受控制的用户输入。 这就是为什么在 Java 6 时代的字符串池主要是在 手动管理的地图。

      Java 7 中的 String.intern()

      Oracle 工程师对字符串进行了极其重要的更改 Java 7 中的池化逻辑——字符串池被重新定位到堆中。 这意味着您不再受限于单独的固定尺寸 内存区。现在所有字符串都位于堆中,就像其他大多数字符串一样 普通对象,它允许您只管理堆大小,同时 调整您的应用程序。从技术上讲,仅此一项就足够了 重新考虑在 Java 7 程序中使用 String.intern() 的原因。 但还有其他原因。

      字符串池值被垃圾回收

      是的,JVM 字符串池中的所有字符串都符合垃圾条件 如果您的程序根目录中没有对它们的引用,请收集。 它适用于所有讨论过的 Java 版本。这意味着如果你的 实习字符串超出范围,没有其他参考 它——它将从 JVM 字符串池中被垃圾回收。

      有资格进行垃圾收集并驻留在堆中,即 JVM 字符串池似乎是您所有字符串的正确位置,不是吗? 理论上这是真的——未使用的字符串将被从 池,使用的字符串将允许您节省内存,以防万一 从输入中获取相等的字符串。似乎是一段完美的回忆 储蓄策略?几乎如此。你一定知道字符串池是怎么回事 在做出任何决定之前实施。

      source.

      【讨论】:

        【解决方案5】:

        正如其他答案所解释的,Java 中的内存分为两部分

        1.堆栈: 每个线程创建一个堆栈,它存储堆栈帧,堆栈帧再次存储局部变量,如果变量是引用类型,则该变量引用实际对象在堆中的内存位置。

        2。堆:各种对象只会在堆中创建。

        堆内存又被分成3部分

        1. Young Generation:存放生命周期较短的对象,Young Generation本身又可以分为Eden SpaceSurvivor Space两大类。

        2。老一代: 存储经过多次垃圾回收循环且仍被引用的对象。

        3.永久代: 存储有关程序的元数据,例如运行时常量池。

        字符串常量池属于Heap内存的永久代区。

        我们可以通过使用javap -verbose class_name在字节码中看到我们代码的运行时常量池,它将向我们显示方法引用(#Methodref)、类对象(#Class)、字符串文字(#String)

        你可以在我的文章How Does JVM Handle Method Overloading and Overriding Internally阅读更多。

        【讨论】:

        【解决方案6】:

        对于此处已经包含的出色答案,我想添加一些我认为缺失的东西 - 插图。

        正如您已经说过的,JVM 将分配给 Java 程序的内存分为两部分。一个是stack,另一个是heap。堆栈用于执行目的,堆用于存储目的。在该堆内存中,JVM 分配了一些专门用于字符串文字的内存。这部分堆内存称为字符串常量池

        例如,如果您初始化以下对象:

        String s1 = "abc"; 
        String s2 = "123";
        String obj1 = new String("abc");
        String obj2 = new String("def");
        String obj3 = new String("456);
        

        字符串文字 s1s2 将进入字符串常量池,对象 obj1、obj2、obj3 进入堆。所有这些,都将从堆栈中引用。

        另外,请注意“abc”将出现在堆和字符串常量池中。为什么String s1 = "abc"String obj1 = new String("abc") 会以这种方式创建?这是因为String obj1 = new String("abc") 显式地创建了一个新的且具有引用意义的String 对象实例,String s1 = "abc" 可以重用字符串常量池中的一个实例(如果可用)。更详细的解释:https://stackoverflow.com/a/3298542/2811258

        【讨论】:

        • 在给定的图表中,文字“def”和“456”存在的位置。这些将如何被引用?
        • 感谢您的评论@Satyendra,我已经更新了插图和答案。
        • @Stas 为什么要创建另一个字符串对象“abc”..它应该使用引用 obj1 来指向文字正确?
        • 这是因为 String obj1 = new String("abc") 显式创建了一个新的且在引用上不同的 String 对象实例,并且 String s1 = "abc" 可以重用字符串常量池中的实例,如果一个可用。更详细的解释:stackoverflow.com/a/3298542/2811258
        猜你喜欢
        • 2020-01-09
        • 2016-09-08
        • 2010-11-15
        • 2013-01-10
        • 2010-10-26
        • 2011-06-25
        • 2013-10-26
        • 1970-01-01
        • 2018-08-27
        相关资源
        最近更新 更多