Java 8的字符串重复数据删除功能

Joe*_*Joe 62 java string java-8

由于String在Java中(像其他语言一样)消耗大量内存,因为每个字符占用两个字节,Java 8引入了一个名为String Deduplication的新功能,它利用了char数组是字符串内部的最终事实,因此JVM可以搞砸他们.

到目前为止,我已经阅读了这个例子,但由于我不是一个专业的java编码器,我很难掌握这个概念.

这是它说的,

已经考虑了各种字符串复制策略,但现在实现的策略遵循以下方法:每当垃圾收集器访问String对象时,它会记录char数组.它接受它们的哈希值并将其与对数组的弱引用一起存储.一旦找到另一个具有相同哈希码的String,就会将它们与char进行比较.如果它们匹配,则将修改一个String并指向第二个String的char数组.然后不再引用第一个char数组,并且可以进行垃圾回收.

整个过程当然会带来一些开销,但是受到严格的限制.例如,如果找不到字符串有一段时间的重复项,则不再检查它.

我的第一个问题,

由于最近在Java 8更新20中添加了这个主题,因此仍然缺乏资源,这里是否有人可以分享一些关于如何帮助减少StringJava 消耗内存的实际示例?

编辑:

上面的链接说,

一旦找到另一个具有相同哈希码的String,就会将它们与char进行比较

我的第二个问题,

如果两个哈希码String相同,则Strings已经是相同的,那么为什么对它们进行比较charchar,一旦发现,这两个String具有相同的散列码?

Rob*_*roj 61

@assylias回答basiclly告诉你它是如何工作的并且是非常好的答案.我已经使用String Deduplication测试了一个生产应用程序,并且有一些结果.网络应用程序大量使用字符串,所以我认为优势非常明显.

要启用String Deduplication,您必须添加这些JVM参数(至少需要Java 8u20):

-XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics
Run Code Online (Sandbox Code Playgroud)

最后一个是可选的,但就像名称所示,它显示了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]
Run Code Online (Sandbox Code Playgroud)

这些是运行应用程序10分钟后的结果.正如您所见,String Deduplication执行了452次,"重复数据删除"执行了801.6 MB字符串.String Deduplication检查了27 000 000个字符串.当我比较了Java 7的我的内存消耗与标准的并行GC到Java 8u20与G1 GC并启用字符串重复数据删除堆下降approximatley 50% :

Java 7并行GC

Java 7并行GC

带有字符串重复数据删除功能的Java 8 G1 GC

带有字符串重复数据删除功能的Java 8 G1 GC

  • 这些图表来自NetBeans IDE - 来自bulit-in profiler.查看Netbeans网站和Google上的教程.或者,您可以从jVisualVM获取相同的图表. (3认同)
  • 什么问题?如果您不能使用G1GC,则无法使用String Deduplication.这没有解决方法. (3认同)

ass*_*ias 53

想象一下,你有一本电话簿,其中包含有a String firstName和a的人String lastName.在你的电话簿中,有10万人拥有相同的功能firstName = "John".

因为您从数据库或文件中获取数据,所以这些字符串不会被实现,因此您的JVM内存包含{'J', 'o', 'h', 'n'}10万次char数组,每个John字符串一个.这些数组中的每一个都占用了20个字节的内存,因此100k Johns占用了2 MB的内存.

通过重复数据删除,JVM将意识到"John"被多次复制并使所有这些John字符串指向相同的底层字符数组,从而将内存使用量从2MB减少到20字节.

您可以在JEP中找到更详细的说明.特别是:

许多大型Java应用程序目前在内存上存在瓶颈.测量表明,在这些类型的应用程序中,大约25%的Java堆实时数据集被String对象使用.此外,这些String对象中大约有一半是重复的,其中重复的意思string1.equals(string2)是真的.从堆上复制String对象本质上只是浪费内存.

[...]

实际预期收益最终减少约10%.请注意,此数字是基于广泛应用的计算平均值.特定应用程序的堆减少量可能会上下变化很大.

  • 在旧版本中,`String`可以使用`int`偏移量和长度来引用数组中的范围.在这种情况下,重复数据删除会复杂得多,但另一方面,`String.substring`的结果不需要它,因为这些子串引用了原始数组.这在Java 7中发生了变化,提高了对重复数据删除功能的需求. (6认同)
  • @Joe您将不得不问JVM设计人员 - 字符串实习已存在很长时间了,我怀疑随着JVM /垃圾收集器的性能变得更好,并且随着每个设备的CPU数量的增加,他们可以改进过去会引入太多开销. (5认同)
  • 你的意思是:_No**,**两个字符串...... _?我的阅读方式截然不同. (4认同)
  • 查找字符串中的子字符串要慢得多(O(n ^ 2)?),而查找两个字符串是否相等则是O(n)最坏情况,而当两个字符串具有不同(缓存)的哈希码时,则为O(1)当时它是一个简单的int比较. (3认同)
  • @Joe我添加了一个链接,可能更好地回答你的问题. (3认同)
  • 我不希望JVM找到子串.但是,如果这样一个较旧的JVM检测到两个`String`s相等,那么它不应该使一个更大的必要数组共享,所以它必须创建并填充一个新数组然后它必须更新`value原子地````````和`count`.相比之下,鉴于它今天的工作方式,只需要改变一个参考. (3认同)
  • @Joe没有两个字符串可以不同,但​​具有相同的哈希码(鸽子洞演示:只有2 ^ 32-1可能的哈希码,你可以创建(几乎)任意数量的字符串).例如,参见[this post](http://stackoverflow.com/questions/2310498/why-doesnt-strings-hashcode-cache-0)获取所有哈希码为0的字符串列表. (3认同)
  • 如果没有相同的char数组怎么办?但100多个不相似的字符串和字符数组?那么这个功能有什么用呢? (2认同)
  • 实际上,这就是我几十年来所做的事情.不知怎的,我希望其他开发人员在从数据库或其他外部资源中读取更大的批处理时,也能够规范化`String`s. (2认同)
  • @Joe在大多数应用程序中你会有很多类似的字符串,例如数据库查询,名称,国家,webservice查询等.它可能不会改进每一个应用程序,但我相信他们已经测试了许多典型的成本与收益在推出该功能之前使用案例. (2认同)
  • @SoririosDelimanolis确实是 - 抱歉丢失的标点符号. (2认同)

mbo*_*007 7

由于您的第一个问题已经得到解答,我将回答您的第二个问题.

String对象必须比较逐个字符,因为虽然等于Object小号意味着等于哈希,逆是不是一定是真的.

正如霍尔格评论中所说,这代表了哈希冲突.

hashcode()方法的适用规范如下:

  • 如果根据equals(Object)方法两个对象相等,则hashCode在两个对象中的每一个上调用方法必须产生相同的整数结果.

  • 如果两个对象根据equals(java.lang.Object)方法不相等则不是必需的,则在两个对象中的hashCode每一个上调用方法必须产生不同的整数结果....

这意味着为了使它们保证相等,每个字符的比较是必要的,以便它们确认两个对象的相等性.它们首先比较hashCodes而不是使用,equals因为它们使用哈希表作为引用,这样可以提高性能.

  • 谢谢你...你能否编辑你的答案并添加一个声明,这是第二个问题的答案...我认为这对未来的读者会有所帮助.:) PS,+ 1 (3认同)
  • 他没有回答你的第二个问题,所以我添加了一些信息,希望对阅读它的人有所帮助和信息. (2认同)
  • 我读了一遍,但我已经看到了接受的答案,所以我只想提供新的信息. (2认同)
  • 解释哈希码的一个好方法是,哈希值是一个有限大小的“整数”。每个整数都可以表示为“字符串”。由此可见,字符串的数量比哈希值的数量要多得多。由于字符串的数量比可能的哈希值多很多倍,因此显然许多字符串必须共享相同的哈希值。 (2认同)