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

a8jjtwal  于 4个月前  发布在  Java
关注(0)|答案(7)|浏览(75)

我知道常量池的概念和JVM用来处理String常量的String常量池,但我不知道JVM用哪种内存来存储String常量。堆栈还是堆?因为它是一个与任何示例都没有关联的字面量,所以我假设它将存储在堆栈中,但是如果它没有被任何示例引用,那么这个字面量必须由GC收集run(如果我错了,请纠正我),那么如果它存储在堆栈中,该如何处理呢?

jmp7cifd

jmp7cifd1#

从技术上讲,答案是两者都不是。根据Java虚拟机规范,存储字符串文字的区域在运行时常量池中。运行时常量池内存区域是在每个类或每个接口的基础上分配的,所以它根本不与任何对象示例绑定。运行时常量池是“方法区域”的一个子集,该区域“存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括在类和示例初始化以及接口类型初始化中使用的特殊方法”。VM规范指出,尽管 * 方法区域 * 在逻辑上是堆的一部分,它并不规定在方法区域中分配的内存要进行垃圾回收或其他与分配给堆。

ctehm74n

ctehm74n2#

正如this answer所解释的那样,字符串池的确切位置并没有指定,并且可能因JVM实现而异。
有趣的是,在Java 7之前,池位于Hotspot JVM堆的permgen空间中,但自Java 7以来,它已被移动到堆的主要部分:

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

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

ha5z0ras

ha5z0ras3#

字符串字面量不存储在堆栈上。从不。事实上,没有对象存储在堆栈上。
字符串文字(或者更准确地说,表示它们的字符串对象)在历史上存储在一个称为“permgen”堆的堆中。(Permgen是永久生成的缩写。)
在正常情况下,字符串文字和permgen堆中的许多其他东西是“永久”可访问的,而不是垃圾收集。(例如,字符串文字总是可以从使用它们的代码对象访问。)但是,您可以配置JVM来尝试查找和收集不再需要的动态加载的类,这可能会导致字符串文字被垃圾收集。

说明#1-我并不是说Permgen不会被GC'ed。它会被GC' ed,通常当JVM决定运行Full GC时。我的观点是,只要使用它们的代码是可访问的,String literals 就可以访问,只要代码的类加载器是可访问的,代码就可以访问,对于默认的类加载器,这意味着“永远”。
验证#2-实际上,Java 7和更高版本使用常规堆来保存字符串池。因此,表示String字面量和intern'd字符串的String对象实际上在常规堆中。(有关详细信息,请参阅@assylias的回答。)

但我仍然试图找出存储字符串字面量和使用new创建的字符串之间的细线。
没有“细线”,其实很简单:

  • 表示/对应于字符串文字的String对象保存在字符串池中。
  • String::intern调用创建的String对象保存在字符串池中。
  • 所有其他的String对象都不保存在字符串池中。

然后是字符串池“存储”在哪里的单独问题。在Java 7之前,它是permgen堆。从Java 7开始,它是主堆。

vvppvyoh

vvppvyoh4#

字符串池

字符串池(有时也称为字符串规范化)是一个用一个共享的String对象替换几个具有相同值但不同标识的String对象的过程。您可以通过保持自己的Map<String,String>(根据您的需求,可能使用软引用或弱引用)并使用map值作为规范化值。这是由JDK提供给你的。
在Java 6的时候,许多标准都禁止使用String.intern(),因为如果池化失控,很可能会得到OutOfMemoryException。Oracle Java 7对字符串池化的实现进行了很大的更改。您可以在https://bugs.java.com/bugdatabase/view_bug?bug_id=6962931https://bugs.java.com/bugdatabase/view_bug?bug_id=6962930中查找详细信息。

Java 6中的String.intern()

在过去的好日子里,所有的intern字符串都存储在PermGen中-堆的固定大小部分,主要用于存储加载的类和字符串池。除了显式intern字符串,PermGen字符串池还包含程序中早期使用的所有文字字符串(这里使用了重要的词-如果类或方法从未加载/调用,则其中定义的任何常量都不会被加载)。
Java 6中这种字符串池的最大问题是它的位置-PermGen。PermGen有一个固定的大小,不能在运行时扩展。你可以使用-XX:MaxPermSize= 96 m选项来设置它。据我所知,默认的PermGen大小在32 M和96 M之间变化,取决于平台。你可以增加它的大小,但它的大小仍然是固定的。这种限制需要非常小心地使用String.intern -你最好不要使用这种方法来intern任何不受控制的用户输入。这就是为什么Java 6时代的字符串池主要是在手动管理的Map中实现的原因。

Java 7中的String.intern()

Oracle工程师对Java 7中的字符串池逻辑做了一个极其重要的改变--字符串池被重新定位到堆中。这意味着你不再受到单独的固定大小内存区域的限制。所有字符串现在都位于堆中,就像大多数其他普通对象一样,这允许你在调优应用程序时只管理堆大小。从技术上讲,仅这一点就足以让你重新考虑在Java 7程序中使用String.intern()。2但还有其他原因。

字符串池值被垃圾回收

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

gywdnpxw

gywdnpxw5#

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

**1.栈:**每个线程创建一个栈,它存储栈帧,栈帧又存储局部变量,如果变量是引用类型,则该变量引用实际对象在堆中的内存位置。
**2.堆:**所有类型的对象都只会在堆中创建。

堆内存再次分为3个部分

1. Young Generation:存放寿命较短的物品,Young Generation本身可分为伊甸园空间幸存者空间两大类。
**2.旧代:**存储经过多次垃圾回收周期后仍被引用的对象。
**3.永久生成:**存储程序的元数据,如运行时常量池。

字符串常量池属于堆内存的永久生成区域。
我们可以通过使用javap -verbose class_name在字节码中看到代码的运行时常量池,它将向我们显示方法引用(#Methodref),类对象(#Class),字符串文字(#String)


的数据
你可以在我的文章How Does JVM Handle Method Overloading and Overriding Internally上读到更多。

ohfgkhjo

ohfgkhjo6#

对于已经包含在这里的伟大答案,我想补充一些在我的观点中缺失的东西-一个例子。
正如你已经看到的,JVM将分配给Java程序的内存分为两部分。一部分是stack,另一部分是heap。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将进入字符串常量池,对象obj 1,obj 2,obj 3进入堆。所有这些都将从堆栈中引用。
另外,请注意,“abc”将出现在堆和字符串常量池中。为什么String s1 = "abc"String obj1 = new String("abc")将以这种方式创建?这是因为String obj1 = new String("abc")显式创建了一个新的、引用上不同的String对象示例,而String s1 = "abc"可以重用字符串常量池中的示例(如果有)。更详细的解释:https://stackoverflow.com/a/3298542/2811258
x1c 0d1x的数据

n3schb8v

n3schb8v7#

考虑到这个问题是很多年前提出的。在那之后有最近的进步,所以分享同样的。
字符串常量池(String Constant Pool,SCP)现在是Java堆内存的一部分(从Java 7开始),所有的字符串文字和内部字符串都驻留在其中。
每当我们创建一个新的变量,如下所示:

var s1 = "year"

字符串
值“year”是一个字符串文字,存储在SCP中,变量s1作为当前运行线程的一部分存储在Stack中。
当线程执行完成时,从堆栈存储器中删除s1。
当GC运行时,它将看到值“year”没有被任何对象/文字引用,它将从那里被垃圾收集。
现在考虑一下,还有一个String对象。

var s2 = new String("year")


s1和s2不会在它们之间共享任何东西。这个对象(s2)不会被存储在SCP中,而是被当作普通的Java对象来处理,因此将被存储在SCP之外的堆空间中。
除非有人正在创建,否则不会/永远不会将其存储到SCP中。

var s3 = s2.intern()


现在,当对象s2不再被程序的任何其他部分引用时,它将再次被垃圾收集。我希望这个答案是有意义的。很高兴在评论中继续讨论。
谢谢

相关问题