我知道常量池的概念和JVM用于处理字符串文字的字符串常量池。但是我不知道JVM使用哪种类型的内存来存储String常量文字。堆栈还是堆?由于它的文字不与任何实例相关联,因此我假定它将存储在堆栈中。但是,如果没有任何实例引用该文字,则必须通过GC运行来收集文字(如果我错了,请纠正我),那么如果将其存储在堆栈中,该如何处理?
我知道常量池的概念和JVM用于处理字符串文字的字符串常量池。但是我不知道JVM使用哪种类型的内存来存储String常量文字。堆栈还是堆?由于它的文字不与任何实例相关联,因此我假定它将存储在堆栈中。但是,如果没有任何实例引用该文字,则必须通过GC运行来收集文字(如果我错了,请纠正我),那么如果将其存储在堆栈中,该如何处理?
Answers:
从技术上来说,答案都不是。根据Java虚拟机规范,用于存储字符串文字的区域位于运行时常量池中。运行时常量池内存区域是按类或每个接口分配的,因此它根本不与任何对象实例绑定。运行时常量池是方法区域的子集,其中“存储每个类的结构,例如运行时常量池,字段和方法数据以及方法和构造函数的代码,包括用于类和实例初始化以及接口的特殊方法”类型初始化”。VM规范说,尽管方法领域 从逻辑上讲,它是堆的一部分,它并不指示在方法区域中分配的内存要进行垃圾回收或其他与分配给堆的常规数据结构相关的行为。
如该答案所解释,字符串池的确切位置未指定,并且可能因一个JVM实现而异。
有趣的是,直到Java 7为止,该池都位于热点JVM上堆的permgen空间中,但是自Java 7以来,该池已移至堆的主要部分:
面积:热点
简介:在JDK 7,实习字符串不再是永久代Java堆的分配,但在Java堆的主要部分被替代分配(被称为老少几代人),与其他一起应用程序创建的对象。此更改将导致更多数据驻留在主Java堆中,而永久生成中的数据更少,因此可能需要调整堆大小。由于此更改,大多数应用程序只会看到相对较小的堆使用差异,但是加载许多类或大量使用String.intern()方法的大型应用程序将看到更大的差异。RFE:6962931
在Java 8 Hotspot中,永久生成已被完全删除。
字符串文字不存储在堆栈中。决不。实际上,没有对象存储在堆栈上。
字符串(或更准确地,代表他们的字符串对象)被在历史存储在堆称为“PermGen的”堆。(Permgen是永久代的缩写。)
在正常情况下,字符串文字和permgen堆中的许多其他内容是“永久”可访问的,并且不会被垃圾收集。(例如,始终可以从使用它们的代码对象访问String文字。)但是,您可以配置JVM尝试查找并收集不再需要的动态加载的类,这可能会导致String文字被垃圾回收。 。
澄清#1-我并不是说Permgen不会进行GC处理。通常在JVM决定运行Full GC时会这样做。我的观点是,只要使用字符串文字的代码可以访问,字符串文字就可以访问;只要代码的类加载器可以访问,那么代码就可以访问;对于默认的类加载器,这意味着“永远”。
澄清#2-实际上,Java 7和更高版本使用常规堆来保存字符串池。因此,代表String文字和intern字符串的String对象实际上在常规堆中。(有关详细信息,请参见@assylias的答案。)
但我仍在尝试找出存储字符串文字和使用创建的字符串之间的细线
new
。
没有“细线”。真的很简单:
String
表示/对应于字符串文字的对象保存在字符串池中。String
String::intern
调用创建的对象保存在字符串池中。String
对象均不保存在字符串池中。然后还有一个单独的问题,即字符串池的“存储位置”。在Java 7之前是permgen堆。从Java 7开始,它是主要堆。
字符串池
字符串池(有时也称为字符串规范化)是用单个共享的String对象替换具有相同值但具有不同标识的多个String对象的过程。您可以通过保留自己的地图(根据需要使用软引用或弱引用)并将地图值用作规范化值来实现此目标。或者,您可以使用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是堆的固定大小部分,主要用于存储加载的类和字符串池。除了显式的固定字符串外,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.永久生成:存储有关程序的元数据,例如运行时常量池。
字符串常量池属于Heap内存的永久生成区域。
通过使用字节码,我们可以在代码中看到代码的运行时常量池,使用javap -verbose class_name
它将向我们显示方法引用(#Methodref),类对象(#Class),字符串文字(#String)
您可以在我的文章JVM如何在内部处理方法重载和覆盖上阅读更多有关它的内容。
对于此处已经包含的出色答案,我想添加我的观点中缺少的内容-插图。
如前所述,JVM将分配给Java程序的内存分为两部分。一个是堆栈,另一个是堆。堆栈用于执行目的,堆用于存储目的。在该堆内存中,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到堆。所有这些都将从堆栈中引用。
另外,请注意,“ abc”将出现在堆和字符串常量池中。为什么String s1 = "abc"
和String obj1 = new String("abc")
将要创建这样?这是因为String obj1 = new String("abc")
显式创建String对象的一个新的且具有参照性的不同实例,并且String s1 = "abc"
可能重复使用字符串常量池中的一个实例(如果有)。有关更详细的说明,请访问:https : //stackoverflow.com/a/3298542/2811258