首页 文章

Java的String常量池在哪里存在,堆还是堆栈?

提问于
浏览
89

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

6 回答

  • 47

    答案在技术上都不是 . 根据Java虚拟机规范,存储字符串文字的区域在runtime constant pool中 . 运行时常量池内存区域是基于每个类或每个接口分配的,因此它根本不依赖于任何对象实例 . 运行时常量池是方法区域的子集"stores per-class structures such as the runtime constant pool, field and method data, and the code for methods and constructors, including the special methods used in class and instance initialization and interface type initialization" . VM规范说尽管方法区域在逻辑上是堆的一部分,但它并没有规定在方法区域中分配的内存会受到垃圾收集或与分配给堆的普通数据结构相关联的其他行为的影响 .

  • 26

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

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

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

    在Java 8 Hotspot中,永久生成已被完全删除 .

  • 6

    字符串文字不存储在堆栈中 .

    字符串文字(或更准确地说,代表它们的String对象)历史上存储在称为"permgen"堆的堆中 . (Permgen是永久性的一代 . )

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

    CLARIFICATION #1 - 我'm not saying that Permgen doesn'得到GC'ed . 它通常在JVM决定运行Full GC时执行 . 我的观点是,只要可以访问使用它们的代码,就可以访问字符串文字,只要代码的类加载器可以访问,代码就可以访问,对于默认的类加载器,这意味着"for ever" .

    CLARIFICATION #2 - 事实上,Java 7存储实习生'd String objects in the regular Heap. This includes (I presume) String object that represent String literals. (See @assylias' s答案了解详情 . )

  • 21

    String pooling

    字符串池(有时也称为字符串规范化)是使用单个共享String对象替换具有相同值但不同标识的多个String对象的过程 . 您可以通过保留自己的Map(根据您的要求可能有软或弱引用)并使用map值作为规范化值来实现此目标 . 或者您可以使用JDK提供给您的String.intern()方法 . 在Java 6时,使用String.intern()被许多标准禁止,因为如果池失控,很可能获得OutOfMemoryException . 字符串池的Oracle Java 7实现发生了很大变化 . 您可以在http://bugs.sun.com/view_bug.do?bug_id=6962931和http://bugs.sun.com/view_bug.do?bug_id=6962930中查找详细信息 .

    String.intern() in Java 6

    在那些美好的旧时代,所有实习字符串都存储在PermGen中 - 堆的固定大小部分主要用于存储加载的类和字符串池 . 除了显式实现的字符串之外,PermGen字符串池还包含程序中先前使用的所有文字字符串(这里使用的重要字 - 如果类或方法是永远不会加载/调用,其中定义的任何常量都不会被加载) . Java 6中这种字符串池的最大问题是它的位置 - PermGen . PermGen具有固定大小,无法在运行时扩展 . 您可以使用-XX:MaxPermSize = 96m选项进行设置 . 据我所知,默认的PermGen大小在32M到96M之间变化,具体取决于平台 . 您可以增加其大小,但其大小仍将固定 . 这种限制需要非常小心地使用String.intern - 你最好不要使用这种方法实现任何不受控制的用户输入 . 这就是为什么Java 6时的字符串池主要在手动管理的映射中实现的原因 .

    String.intern() in Java 7

    Oracle工程师对Java 7中的字符串池逻辑进行了非常重要的更改 - 字符串池已重新定位到堆中 . 这意味着您不再受限于单独的固定大小的内存区域 . 所有字符串现在都位于堆中,与大多数其他普通对象一样,它允许您在调整应用程序时仅管理堆大小 . 从技术上讲,仅此一点可能是重新考虑在Java 7程序中使用String.intern()的充分理由 . 但还有其他原因 .

    String pool values are garbage collected

    是的,如果程序根目录中没有对它们的引用,则JVM字符串池中的所有字符串都有资格进行垃圾回收 . 它适用于所有讨论过的Java版本 . 这意味着如果您的实习字符串超出范围并且没有其他引用 - 它将从JVM字符串池中进行垃圾收集 . 有资格进行垃圾收集并驻留在堆中,JVM字符串池似乎是所有字符串的正确位置,不是吗?理论上确实如此 - 未使用的字符串将从池中进行垃圾收集,使用的字符串将允许您节省内存,以防从输入中获得相等的字符串 . 似乎是一个完美的记忆保存策略?几乎如此 . 在做出任何决定之前,您必须知道如何实现字符串池 .

    source.

  • 6

    对于已经包含在这里的伟大答案,我想在我的视角中添加一些缺失的东西 - 和插图 .

    因为您已经将JVM将分配的内存分为两部分 . 一个是 stack ,另一个是 heap . 堆栈用于执行目的,堆用于存储目的 . 在该堆内存中,JVM分配了一些专门用于字符串文字的内存 . 这部分堆内存称为 string constants pool .

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

    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到堆 . 所有这些都将从Stack中引用 .

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

    enter image description here

  • 61

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

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

    2. Heap: 将仅在堆中创建所有类型的对象 .

    堆内存再次分为3个部分

    1. Young Generation: 存储寿命短的对象,Young Generation本身可以分为两类 Eden SpaceSurvivor Space .

    2. Old Generation: 存储在许多垃圾收集周期中仍然存在且仍在引用的对象 .

    3. Permanent Generation: 存储有关该程序的元数据,例如运行时常量池 .

    字符串常量池属于堆内存的永久生成区域 .

    正如How Does JVM Handle Method Overloading and Overriding Internally中所讨论的,我们可以通过使用 javap -verbose class_name 来查看字节码中代码的运行时常量池,它将向我们展示方法引用(#Methodref),类对象(#Class),字符串文字(#String)

    runtime-constant-pool

相关问题