我知道常量池的概念和JVM用于处理String文字的String常量池。但我不知道JVM使用哪种类型的内存来存储String常量文字。堆栈还是堆?由于它是一个与任何实例无关的文字,我会认为它将存储在堆栈中。但是如果它没有被任何实例引用,那么必须通过GC运行收集文字(如果我错了,请纠正我),那么如果它存储在堆栈中怎么处理呢?
答案在技术上都不是。根据Java虚拟机规范,存储字符串文字的区域在runtime constant pool中。运行时常量池内存区域是基于每个类或每个接口分配的,因此它根本不依赖于任何对象实例。运行时常量池是方法区域的子集,它“存储每类结构,例如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化和接口中使用的特殊方法类型初始化“。 VM规范说虽然方法区域在逻辑上是堆的一部分,但它并没有规定在方法区域中分配的内存会受到垃圾收集或与分配给堆的普通数据结构相关联的其他行为的影响。
正如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中,永久生成已被完全删除。
字符串文字不存储在堆栈中。
字符串文字(或更准确地说,代表它们的String对象) 是 历史上存储在称为“permgen”堆的堆中。 (Permgen是永久性的一代。)
在正常情况下,字符串文字和permgen堆中的许多其他内容都是“永久”可访问的,并且不会被垃圾回收。 (例如,可以从使用它们的代码对象中访问字符串文字。)但是,您可以配置JVM以尝试查找和收集不再需要的动态加载的类,这可能导致字符串文字被垃圾收集。
澄清#1 - 我不是说Permgen没有得到GC。它通常在JVM决定运行Full GC时执行。我的观点是,只要可以访问使用它们的代码,就可以访问字符串文字,只要代码的类加载器可以访问,代码就可以访问,对于默认的类加载器,这意味着“永远”。
澄清#2 - 事实上,Java 7在常规堆中存储了实际的String对象。这包括(我推测)表示String文字的String对象。 (有关详细信息,请参阅@ assylias的答案。)
字符串池
字符串池(有时也称为字符串规范化)是使用单个共享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中查找详细信息。
Java 6中的String.intern()
在那些美好的旧时代,所有实习字符串都存储在PermGen中 - 堆的固定大小部分主要用于存储加载的类和字符串池。除了显式内部字符串之外,PermGen字符串池还包含程序中较早使用的所有文字字符串(此处使用的重要字 - 如果从未加载/调用类或方法,则不会加载其中定义的任何常量)。
Java 6中这种字符串池的最大问题是它的位置 - PermGen。 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字符串池似乎是所有字符串的正确位置,不是吗?理论上确实如此 - 未使用的字符串将从池中进行垃圾收集,使用的字符串将允许您节省内存,以防从输入中获得相等的字符串。似乎是一个完美的记忆保存策略?几乎如此。在做出任何决定之前,您必须知道如何实现字符串池。
正如其他答案所解释的那样,Java中的内存分为两部分
1.堆栈:每个线程创建一个堆栈,它存储堆栈帧,再次存储局部变量,如果变量是引用类型,则该变量引用堆中实际对象的内存位置。
2.堆:只在堆中创建所有类型的对象。
堆内存再次分为3个部分
1.年轻一代:存储生命短暂的物体,年轻一代本身可分为两类伊甸园空间和幸存者空间。
2.旧生成:存储在许多垃圾收集周期中存活但仍被引用的对象。
3.永久生成:存储关于程序的元数据,例如运行时常量池。
字符串常量池属于堆内存的永久生成区域。
我们可以通过使用javap -verbose class_name
看到字节码中代码的运行时常量池,它将向我们展示方法引用(#Methodref),类对象(#Class),字符串文字(#String)
您可以在我的文章How Does JVM Handle Method Overloading and Overriding Internally上阅读更多相关信息。
对于已经包含在这里的伟大答案,我想在我的视角中添加一些缺失的东西 - 插图。
因为您已经将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中引用。
另请注意,“abc”将出现在堆和字符串常量池中。为什么String s1 = "abc"
和String obj1 = new String("abc")
将以这种方式创建?这是因为String obj1 = new String("abc")
显式创建了一个String对象的新的和引用不同的实例,并且String s1 = "abc"
可以重用字符串常量池中的实例(如果有)。有关更详细的解释:https://stackoverflow.com/a/3298542/2811258