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[]
数组,因为String
s 是作为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<Integer, Integer> map = new HashMap<>();
进行测量,它的重量似乎是 48 个字节——实际上没有对象(但有一些原语,如 size
、@987654333 @等)。
@Holger 但只要我添加一个条目,大小就会增长到 176 字节......确实有一个 Node
s (Entry
)、Key
和 @ 的数组987654337@、Node
和 HashMap
本身 - 更像是 5
@Eugene:好吧,一个数组可能有相当大的大小,高达千兆字节,但只有一个数组。键和值不计算在内,因为它们必须存在之前,您可以使用它们作为参数调用put
,并且仍然可以在地图外使用。生成已经提到的三个对象,HashMap
本身、数组(默认容量为16
)和条目(又名节点)。您可以通过调用keySet()
、entrySet()
和values()
来提高内存消耗,因为这些视图会被记住以供重复使用。【参考方案3】:
每个Java String
对象都有一个private final char value[];
代表String
的不可变内容(参见源代码)。
这似乎反映了您在堆分析中看到的内容。
【讨论】:
是的,我知道,但问题是如果 char[] 也是一个对象,那么对象的总数是多少? @LoneWolf “问题是 char[] 是否也是一个对象”所有数组都是 Java 中的对象。 @LoneWolf 是的,原始字符串文字 "ABC" 和包含字母A
B
和 C
的数组以及您通过 new String(...)
创建的其他字符串,它复制对相同字符数组的引用.所以这段代码有 2 个字符串对象和 1 个公共字符数组。【参考方案4】:
表示内场
char[]value
没有关于游泳池。
而新的 String 构造函数复制了原来的字符串,已经是字符串了
【讨论】:
以上是关于JAVA-8 中堆上的字符串对象数的主要内容,如果未能解决你的问题,请参考以下文章
如何删除 ServiceHost 持有的大对象堆上的 Byte[]
String s = new String(“xyz”);创建了几个字符串对象?