JAVA-8 中堆上的字符串对象数

Posted

技术标签:

【中文标题】JAVA-8 中堆上的字符串对象数【英文标题】:Number of String Objects on heap in JAVA-8 【发布时间】:2018-03-06 13:02:48 【问题描述】:

从这个关于堆栈溢出的Number of String Objects ,我知道如果我们做一些类似的事情:

String s = new String("ABC");

然后我们有两个objects 一个在堆上,即String,一个在constant 池上,即"ABC"

但是今天我拿了堆转储,发现堆上有两个objects。我使用了 MAT 工具,请在下面找到屏幕截图。

所以我的查询是,如果堆上有两个对象,一个是 Char[],另一个是 String 类,一个是常量池,那么这意味着

String s = new String("ABC") 一共会创建 3 个对象。

【问题讨论】:

鉴于您的 cmets,我建议您编辑问题以表达您的特殊关注或兴趣。检查像 String 这样的类的内部通常是不值得的,因为在面向对象编程中,这种内部在某种意义上“不关我们的事”。事实上,String 类的实现和处理在 Java 9 中发生了显着变化 【参考方案1】:

互联网上似乎有关于字符串文字和字符串池的重复废话。只是为了强调,heap 是如何定义的:

The Java® Virtual Machine Specification

2.5.3。堆

Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的。堆是为所有类实例和数组分配内存的运行时数据区域。

因此,无论虚拟机如何实现它,所有对象都存在于堆中,这就是术语 heap 的定义方式。堆是内存,所有对象实例都是从其中分配的,因此,所有对象都是从堆中分配的。过去,字符串字面量的对象和通过new 创建的对象曾经存在于不同的内存区域中,但所有这些仍然是堆的一部分。

在最近的 JVM 中,所有 String 实例都创建在同一内存区域中,无论是为文字创建的还是通过 new 创建的实例。

在任何一种情况下,用于管理字符串字面量和“内部”字符串的字符串池都是对这些字符串的引用表。表本身可能位于堆外,而对象则不然。

在您的示例中,您有两个String 实例和一个char[] 数组,因为Strings 是作为char[] 数组的包装器实现的,并且两个字符串共享该数组。但这是一个实现细节。在其他(较旧的)JVM 中,当您使用 String(String) 构造函数从另一个字符串构造一个字符串时,该数组被复制。因此,在这些 JVM 中,您的示例将创建两个 String 实例和两个 char[] 数组实例。

更奇特的是,使用最新的 JVM 和适当的配置,JVM 将识别具有不同数组但内容相同的 String 实例,并将它们更改为共享数组以减少内存消耗。此功能称为String Deduplication。在 Stack Overflow 上,请参阅:String Deduplication feature of Java 8

【讨论】:

【参考方案2】:

char[]String 的内部字段(毕竟,它必须存储字符某处)。

在询问“创建了多少对象”时,不计算内部字段。

如果问这个创建了多少对象:

Map<Integer, Integer> map = new HashMap<>();

共识将是“1”。在内部,创建了许多对象(我没有对其进行分析,但我猜测会超过 20 个),但实现选择不是问题的一部分。

【讨论】:

@LoneWolf 如果您计算 每个 对象,那么还会有更多。这不是“创建了多少对象”类型问题的意图。 鉴于当前的实现,新构建的HashMap 真正由HashMap 实例组成,仅此而已。所有其他工件都是惰性填充的。即使在其中放入一些东西,足迹也远没有那么昂贵。放置第一个条目会导致分配一个数组(最初是 all-null)和一个 Entry 实例。仍然只制作三个对象。对于随后的每个put,将创建一个额外的Entry 实例。好吧,猜测“创建了多少对象”类型问题的意图毫无结果…… @Holger 我发现这是一个有趣的陈述,并用jol 和一个空的Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;(); 进行测量,它的重量似乎是 48 个字节——实际上没有对象(但有一些原语,如 size、@987654333 @等)。 @Holger 但只要我添加一个条目,大小就会增长到 176 字节......确实有一个 Nodes (Entry)、Key 和 @ 的数组987654337@、NodeHashMap 本身 - 更像是 5 @Eugene:好吧,一个数组可能有相当大的大小,高达千兆字节,但只有一个数组。键和值不计算在内,因为它们必须存在之前,您可以使用它们作为参数调用put,并且仍然可以在地图外使用。生成已经提到的三个对象,HashMap 本身、数组(默认容量为16)和条目(又名节点)。您可以通过调用keySet()entrySet()values() 来提高内存消耗,因为这些视图会被记住以供重复使用。【参考方案3】:

每个Java String 对象都有一个private final char value[]; 代表String 的不可变内容(参见源代码)。

这似乎反映了您在堆分析中看到的内容。

【讨论】:

是的,我知道,但问题是如果 char[] 也是一个对象,那么对象的总数是多少? @LoneWolf “问题是 char[] 是否也是一个对象”所有数组都是 Java 中的对象。 @LoneWolf 是的,原始字符串文字 "ABC" 和包含字母 A BC 的数组以及您通过 new String(...) 创建的其他字符串,它复制对相同字符数组的引用.所以这段代码有 2 个字符串对象和 1 个公共字符数组。【参考方案4】:

表示内场 char[]value 没有关于游泳池。 而新的 String 构造函数复制了原来的字符串,已经是字符串了

【讨论】:

以上是关于JAVA-8 中堆上的字符串对象数的主要内容,如果未能解决你的问题,请参考以下文章

函数中堆分配与缺少堆分配的影响

如何删除 ServiceHost 持有的大对象堆上的 Byte[]

堆上的结构? [复制]

String s = new String(“xyz”);创建了几个字符串对象?

C中堆管理——浅谈malloc,calloc,realloc函数之间的区别

C中堆管理——浅谈malloc,calloc,realloc函数之间的区别