我知道常量池的概念和JVM用于处理String文字的String常量池。但我不知道JVM使用哪种类型的内存来存储String常量文字。堆栈还是堆?由于它是一个与任何实例无关的文字,我会认为它将存储在堆栈中。但是如果它没有被任何实例引用,则必须通过GC运行收集文字(如果我错了,请纠正我),那么如果它存储在堆栈中怎么处理呢?
答案 0 :(得分:65)
答案在技术上都不是。根据Java虚拟机规范,存储字符串文字的区域在runtime constant pool中。运行时常量池内存区域是基于每个类或每个接口分配的,因此它根本不依赖于任何对象实例。运行时常量池是方法区域的子集,它“存储每类结构,例如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括使用的特殊方法在类和实例初始化和接口类型初始化“。 VM规范说虽然方法区域在逻辑上是堆的一部分,但它并没有规定在方法区域中分配的内存会受到垃圾收集或与普通数据相关的其他行为的影响。分配给堆的结构。
答案 1 :(得分:50)
正如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中,永久生成已被完全删除。
答案 2 :(得分:29)
字符串文字不存储在堆栈中。
字符串文字(或者更准确地说,代表它们的String对象) 历史上存储在称为“permgen”堆的Heap中。 (Permgen是永久性生成的缩写。)
在正常情况下,字符串文字和permgen堆中的许多其他内容都是“永久”可访问的,并且不会被垃圾回收。 (例如,可以从使用它们的代码对象中访问字符串文字。)但是,您可以配置JVM以尝试查找和收集不再需要的动态加载的类,这可能导致字符串文字被垃圾收集
澄清#1 - 我不是说Permgen没有得到GC。它通常在JVM决定运行Full GC时执行。我的观点是只要可以访问使用它们的代码,就可以访问String literals ,只要代码的类加载器可以访问,代码就可以访问,对于默认的类加载器,这意味着“永远”。
澄清#2 - 实际上,Java 7在常规堆中存储了实际的String对象。这包括(我推测)表示String文字的String对象。 (有关详细信息,请参阅@ assylias的答案。)
答案 3 :(得分:23)
字符串合并
字符串池(有时也称为字符串规范化)是一种 用相同的值替换几个String对象的过程但是 具有单个共享String对象的不同标识。你可以实现 保持自己的地图(可能是软的) 或弱参考,取决于您的要求)和使用地图 值作为规范化的值。或者您可以使用String.intern()方法 由JDK提供给您。
在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
Java 6中的String.intern()
Java 7中在那些美好的旧时代,所有实习字符串都存储在PermGen中 - 堆的固定大小部分主要用于存储加载的类 和字符串池。除了明确的实习字符串,PermGen字符串 pool还包含程序中先前使用的所有文字字符串 (这里使用的重要词 - 如果一个类或方法永远不会 加载/调用,其中定义的任何常量都不会被加载。
Java 6中这种字符串池的最大问题是它的位置 - PermGen。 PermGen具有固定的大小,无法扩展 运行。您可以使用-XX:MaxPermSize = 96m选项进行设置。就我而言 知道,默认的PermGen大小在32M到96M之间变化,具体取决于 该平台。你可以增加它的大小,但它的大小仍然是 固定。这种限制需要非常小心地使用String.intern - 你最好不要使用这种方法实习任何不受控制的用户输入。 这就是为什么Java 6时代的字符串池主要实现的原因 手动管理的地图。
String.intern()
Oracle工程师对字符串进行了非常重要的更改 Java 7中的池化逻辑 - 字符串池被重定位到堆中。 这意味着您不再受限于单独的固定尺寸 记忆区。所有字符串现在都位于堆中,与其他大多数字符串一样 普通对象,它允许您只管理堆大小 调整你的申请。从技术上讲,仅此一点就足够了 有理由重新考虑在Java 7程序中使用String.intern()。 但还有其他原因。
字符串池值是垃圾回收
是的,JVM字符串池中的所有字符串都有资格使用垃圾 如果程序根目录中没有对它们的引用,则为集合。 它适用于所有讨论过的Java版本。这意味着,如果你的 实习字符串超出范围,没有其他引用 它 - 它将从JVM字符串池中收集垃圾。
有资格进行垃圾收集并驻留在堆中,即JVM 字符串池似乎是所有字符串的正确位置,不是吗? 理论上确实如此 - 未使用的字符串将被垃圾收集 池,使用的字符串将允许您保存内存以防万一 从输入中获取一个相等的字符串。似乎是一个完美的记忆 储蓄策略?几乎如此。您必须知道字符串池是如何的 在做出任何决定之前实施。
答案 4 :(得分:9)
正如其他答案所解释的那样,Java中的内存分为两部分
<强> 1。堆栈:每个线程创建一个堆栈,它存储堆栈帧,再次存储局部变量,如果变量是引用类型,则该变量引用堆中实际对象的内存位置。
<强> 2。堆:所有类型的对象都只在堆中创建。
堆内存再次分为3个部分
<强> 1。年轻一代:存储生命短暂的物体,年轻一代本身可以分为两类<强>伊甸园空间和幸存者空间。
<强> 2。老一代:存储在许多垃圾收集周期中存活但仍被引用的对象。
第3。永久生成:存储有关该计划的元数据,例如运行时常量池。
字符串常量池属于堆内存的永久生成区域。
我们可以使用javap -verbose class_name
看到字节码中代码的运行时常量池,它将显示方法引用(#Methodref),类对象(#Class),字符串文字(#String)
您可以在我的文章How Does JVM Handle Method Overloading and Overriding Internally上阅读更多相关信息。
答案 5 :(得分:7)
对于已经包含在这里的好答案,我想在我的视角中添加一些缺失的东西 - 插图。
因为您已经将JVM的分配内存分为两部分。一个是堆栈,另一个是堆。堆栈用于执行目的,堆用于存储目的。在该堆内存中,JVM分配了一些专门用于字符串文字的内存。堆内存的这一部分称为字符串常量池。
例如,如果您初始化以下对象:
String s1 = "abc";
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);
字符串文字s1
和s2
将转换为字符串常量池,对象obj1,obj2,obj3到堆。所有这些都将从Stack中引用。
另外,请注意&#34; abc&#34;将出现在堆和字符串常量池中。为什么String s1 = "abc"
和String obj1 = new String("abc")
会以这种方式创建?这是因为String obj1 = new String("abc")
显式创建了String对象的新的和引用不同的实例,String s1 = "abc"
可以重用字符串常量池中的实例(如果有)。有关更详细的解释:https://stackoverflow.com/a/3298542/2811258