Java的String常量池在哪里生活,堆或栈?

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

答案在技术上不是。 根据Java虚拟机规范,用于存储string文字的区域位于运行时常量池中 。 运行时常量池内存区按每个类或每个接口进行分配,因此它不受任何对象实例的束缚。 运行时常量池是方法区域的一个子集,它“存储每类结构,如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化和接口中使用的特殊方法types初始化“。 VM规范说,尽pipe方法区域在逻辑上是堆的一部分,但并不表示在方法区域中分配的内存将受到垃圾回收或与分配给堆的正常数据结构相关的其他行为的影响。

正如这个答案所解释的,string池的确切位置没有被指定,并且可以从一个JVM实现到另一个不同。

有趣的是,在Java 7之前,池已经在热点JVM上的堆的permgen空间中,但自从Java 7以来它已经被移动到堆的主要部分 :

地区 :HotSpot
概要 :在JDK 7中,internedstring不再分配在Java堆的永久生成中, 而是分配到Java堆的主要部分 (称为年轻人和老一代),以及由其创build的其他对象应用程序。 这种改变将导致更多的数据驻留在主Java堆中,永久代中的数据更less,因此可能需要调整堆大小。 由于这种变化,大多数应用程序在堆使用中只会看到相对较小的差异,但是加载很多类或大量使用String.intern()方法的较大应用程序将会看到更显着的差异。 RFE:6962931

在Java 8 Hotspot中,Permanent Generation已经被彻底删除。

string文字不存储在堆栈中。

string文字(或更准确地说,表示它们的String对象)历史被存储在称为“permgen”堆的Heap中。 (Permgen是永久代的简称)

在正常情况下,string文字和许多其他东西在permgen堆是“永久”可达,并没有垃圾收集。 (例如,string文字总是可以从使用它们的代码对象中获得)。但是,您可以configurationJVM以尝试查找和收集不再需要的dynamic加载的类,这可能会导致string文本被垃圾收集。

澄清#1 – 我并不是说Permgen不会被GC检测出来。 它通常在JVM决定运行Full GC时。 我的意思是,只要使用它们的代码是可达的,只要代码的类加载器是可到达的,那么string文本就是可达的,对于默认的类加载器,这意味着“永远”。

CLARIFICATION#2 – 实际上,Java 7在常规堆中存储内部string对象。 这包括(我假设)代表string文字的string对象。 (详情请见@ assylias's Answer。)

string池

string池(有时也称为string规范化)是用一个共享的string对象replace几个具有相同值但不同身份的string对象的过程。 您可以通过保留自己的地图(可能根据您的要求使用软或弱引用)并将地图值用作规范化值来实现此目标。 或者您可以使用由JDK提供给您的String.intern()方法。

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

Java 6中的String.intern()

在那些古老的日子里,所有的internedstring被存储在PermGen中 – 堆的固定大小的部分主要用于存储加载的类和string池。 除了明确的string外,PermGenstring池还包含了程序中早期使用的所有string(这里使用了重要的词 – 如果一个类或方法从未被加载/调用过,那么定义的常量将不会被加载)。

Java 6中这种string池的最大问题是它的位置 – PermGen。 PermGen具有固定的大小,不能在运行时扩展。 您可以使用-XX:MaxPermSize = 96m选项进行设置。 据我所知,根据平台的不同,默认的PermGen大小在32M到96M之间不等。 你可以增加它的大小,但它的大小仍然是固定的。 这样的限制需要非常小心地使用String.intern – 你最好不要使用这种方法实习任何不受控制的用户input。 这就是为什么Java 6时代的string池大部分是在手动pipe理的地图中实现的。

Java 7中的String.intern()

Oracle工程师对Java 7中的string池逻辑做了一个非常重要的改变 – string池被重新定位到堆中。 这意味着您不再受限于单独的固定大小的内存区域。 所有string现在都位于堆中,与大多数其他普通对象一样,这使您可以在调整应用程序时仅pipe理堆大小。 从技术上讲,这仅仅是在Java 7程序中重新考虑使用String.intern()的充分理由。 但还有其他原因。

string池值被垃圾收集

是的,如果JVMstring池中的所有string都没有对程序根目录的引用,那么它们就有资格进行垃圾回收。 它适用于所有讨论的Java版本。 这意味着如果你的实例string超出了范围,并且没有其他引用,它将从JVMstring池中进行垃圾收集。

有资格进行垃圾收集和在堆中居住,JVMstring池似乎是所有string的正确位置,不是吗? 理论上它是真的 – 未使用的string将从池中收集垃圾,使用的string将允许您保存内存,以防input相同的string。 似乎是一个完美的记忆节省策略? 差不多如此。 在作出任何决定之前,您必须知道string池是如何实现的。

资源。

对于已经包含在这里的很好的答案,我想补充一些在我的观点和例证中缺less的东西。

由于您已经将JVM分配给Java程序的内存分为两部分。 一个是堆栈 ,另一个是 。 堆栈用于执行目的,堆用于存储目的。 在这个堆内存中,JVM分配一些专门用于string文本的内存。 这部分堆内存被称为string常量池

例如,如果你初始化下面的对象:

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

string文字s1s2将去往string常量池,对象obj1,obj2,obj3到堆中。 所有这些将从堆栈中引用。

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

在这里输入图像描述