[英]String Deduplication feature of Java 8
由于Java中的String
(与其他语言一样)消耗大量内存,因为每个字符占用两个字节,因此Java 8引入了一个名为String Deduplication的新功能,该功能利用了char数组在字符串内部和最终字符串的事实,因此JVM可以搞砸它们。
到目前为止,我已经阅读了这个例子 ,但由于我不是一个专业的java编码器,我很难掌握这个概念。
这是它说的,
已经考虑了各种字符串复制策略,但现在实现的策略遵循以下方法:每当垃圾收集器访问String对象时,它会记录char数组。 它接受它们的哈希值并将其与对数组的弱引用一起存储。 一旦找到另一个具有相同哈希码的String,就会将它们与char进行比较。 如果它们匹配,则将修改一个String并指向第二个String的char数组。 然后不再引用第一个char数组,并且可以进行垃圾回收。
整个过程当然会带来一些开销,但是受到严格的限制。 例如,如果找不到字符串有一段时间的重复项,则不再检查它。
我的第一个问题,
由于最近在Java 8更新20中添加了这个主题,因此仍然缺乏资源,这里是否有人可以分享一些实际示例,说明它如何帮助减少Java中String
消耗的内存?
编辑:
上面的链接说,
一旦找到另一个具有相同哈希码的String,就会将它们与char进行比较
我的第二个问题,
如果两个哈希代码String
是相同的,则Strings
已经是相同的,那么为什么对它们进行比较char
通过char
一旦发现这两个String
具有相同的哈希码?
@assylias回答basiclly告诉你它是如何工作的并且是非常好的答案。 我已经使用String Deduplication测试了一个生产应用程序,并且有一些结果。 网络应用程序大量使用字符串,所以我认为优势非常明显。
要启用String Deduplication,您必须添加这些JVM参数(至少需要Java 8u20):
-XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics
最后一个是可选的,但就像名称所示,它显示了String Deduplication统计信息。 这是我的:
[GC concurrent-string-deduplication, 2893.3K->2672.0B(2890.7K), avg 97.3%, 0.0175148 secs]
[Last Exec: 0.0175148 secs, Idle: 3.2029081 secs, Blocked: 0/0.0000000 secs]
[Inspected: 96613]
[Skipped: 0( 0.0%)]
[Hashed: 96598(100.0%)]
[Known: 2( 0.0%)]
[New: 96611(100.0%) 2893.3K]
[Deduplicated: 96536( 99.9%) 2890.7K( 99.9%)]
[Young: 0( 0.0%) 0.0B( 0.0%)]
[Old: 96536(100.0%) 2890.7K(100.0%)]
[Total Exec: 452/7.6109490 secs, Idle: 452/776.3032184 secs, Blocked: 11/0.0258406 secs]
[Inspected: 27108398]
[Skipped: 0( 0.0%)]
[Hashed: 26828486( 99.0%)]
[Known: 19025( 0.1%)]
[New: 27089373( 99.9%) 823.9M]
[Deduplicated: 26853964( 99.1%) 801.6M( 97.3%)]
[Young: 4732( 0.0%) 171.3K( 0.0%)]
[Old: 26849232(100.0%) 801.4M(100.0%)]
[Table]
[Memory Usage: 2834.7K]
[Size: 65536, Min: 1024, Max: 16777216]
[Entries: 98687, Load: 150.6%, Cached: 415, Added: 252375, Removed: 153688]
[Resize Count: 6, Shrink Threshold: 43690(66.7%), Grow Threshold: 131072(200.0%)]
[Rehash Count: 0, Rehash Threshold: 120, Hash Seed: 0x0]
[Age Threshold: 3]
[Queue]
[Dropped: 0]
这些是运行应用程序10分钟后的结果。 正如您所见,String Deduplication执行了452次,“重复数据删除”执行了801.6 MB字符串。 String Deduplication检查了27 000 000个字符串。 当我将Java 7中的内存消耗与标准并行GC与Java 8u20与G1 GC和启用的字符串重复数据删除进行比较时,堆下降了大约50% :
Java 7并行GC
带有字符串重复数据删除功能的Java 8 G1 GC
想象一下,你有一本电话簿,其中包含人物,它们具有String firstName
和String lastName
。 而且在你的电话簿中,有10万人拥有相同的firstName = "John"
。
因为您从数据库或文件中获取数据,所以这些字符串不会被实现,因此您的JVM内存包含char数组{'J', 'o', 'h', 'n'}
10万次,每个John字符串一个。 这些数组中的每一个都占用了20个字节的内存,因此100k Johns占用了2 MB的内存。
通过重复数据删除,JVM将意识到“John”被多次复制,并使所有这些John字符串指向相同的底层字符数组,从而将内存使用量从2MB减少到20字节。
您可以在JEP中找到更详细的说明。 特别是:
许多大型Java应用程序目前在内存上存在瓶颈。 测量表明,在这些类型的应用程序中,大约25%的Java堆实时数据集被String对象使用。 此外,这些String对象中大约有一半是重复的,其中重复表示
string1.equals(string2)
为true。 从堆上复制String对象本质上只是浪费内存。[...]
实际预期收益最终减少约10%。 请注意,此数字是基于广泛应用的计算平均值。 特定应用程序的堆减少量可能会上下变化很大。
由于您的第一个问题已经得到解答,我将回答您的第二个问题。
该String
对象必须比较逐个字符,因为虽然等于Object
小号意味着等于哈希,逆不一定是真实的。
hashcode()
方法的适用规范如下:
如果两个对象根据
equals(Object)
方法equals(Object)
,则对两个对象中的每一个调用hashCode
方法必须生成相同的整数结果。如果两个对象根据
equals(java.lang.Object)
方法不相等,则不需要在两个对象中的每一个上调用hashCode
方法必须生成不同的整数结果。 ...
这意味着为了使它们保证相等,每个字符的比较是必要的,以便它们确认两个对象的相等性。 它们首先比较hashCode
而不是equals
因为它们使用哈希表作为引用,这样可以提高性能。
他们描述的策略是简单地在可能多个equal
字符串中重用一个String的内部字符数组。 如果每个String都相同,则不需要拥有自己的副本。
为了更快速地确定是否2个字符串相等,哈希码作为第一步骤,因为它是一个快速的方法,以确定是否字符串可以相等。 因此他们的声明:
一旦找到另一个具有相同哈希码的String,就会将它们与char进行比较
这是为了在使用哈希码确定可能的相等性时对相等进行某种 (但较慢)的比较。
最后,相等的字符串将共享一个底层字符数组。
Java已经有很长一段时间的String.intern()
,或多或少相同(即通过重复删除相同的字符串来节省内存)。 关于这一点的新颖之处在于它发生在垃圾收集时间并且可以从外部控制。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.