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


73

由于String在Java(与其他语言一样)中由于每个字符占用两个字节而占用大量内存,因此Java 8引入了一项称为String Deduplication的新功能,该功能利用了char数组在字符串和final内部的事实,因此JVM会和他们混在一起。

到目前为止,我已经阅读了这个示例,但是由于我不是专业的Java程序员,因此我很难理解这个概念。

它是这样说的,

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

这整个过程当然会带来一些开销,但是受到严格的限制。例如,如果一段时间内未发现重复字符串,则将不再检查该字符串。

我的第一个问题

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

编辑:

上面的链接说,

一旦找到另一个具有相同哈希码的字符串,便将它们逐个字符地进行比较

我的第二个问题

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


37
你们每个人都听说过“哈希冲突”吗?只有2³² == 4294967296不同的哈希码,但65536²¹⁴⁷⁴⁸³⁶⁴⁸ == practically infinite可能有不同String的。换句话说,具有相同的哈希码不能保证String相等。你必须检查一下。唯一相反的是,具有不同的哈希码意味着Strings不相等。
Holger

13
我没有链接,因为它很容易找出:一个char是16位的值,因此它允许2¹⁶ == 65536组合。AString是一个具有int长度的序列,因此它最多可以包含2³¹字符(2³¹不是2³²因为int用Java签名,而是String一个正数),因此最大String长度是2³¹ == 2147483648(理论上,实际限制要小一些)。因此,一个aString最多可以组合2147483648个char,这些char可以有65536个可能的组合,这使得65536²¹⁴⁷⁴⁸³⁶⁴⁸组合(实际上稍微大一点,String也可能更短)
Holger 2015年

6
@ mbomb007:它就像具有与数n位位置时,有m不同的数字,其允许mⁿ组合,例如,从十进制数000999允许10³组合。对于a String,在数字位置有65536不同的“数字”(又名chars)2147483648,因此其65536²¹⁴⁷⁴⁸³⁶⁴⁸。它只是“略微”多一点,\0而且在Java中“字符串结尾”是不同的。没关系,因为它太大了,无法想象。
Holger

3
如果包含String可以更短的a ,则它应等于(2¹⁶)^(∑ n = 0_31(2 ^ n))。我正是这个意思。其实还不算多。
mbomb007

3
相等的哈希码并不意味着相等的字符串。见stackoverflow.com/questions/27581/...
Raedwald

Answers:


64

假设您有一本电话簿,其中包含人,其中有一个String firstName和一个String lastName。碰巧在您的电话簿中,有100,000个人拥有相同的知识firstName = "John"

由于您是从数据库或文件中获取数据的,因此这些字符串不会被中断,因此您的JVM内存包含char数组{'J', 'o', 'h', 'n'}10万次,每个John字符串一次。每个阵列占用20个字节的内存,因此,这些100k Johns占用2 MB的内存。

通过重复数据删除,JVM将认识到“ John”已被重复多次,并使所有这些John字符串指向同一基础char数组,从而将内存使用量从2MB减少到20个字节。

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

当前,许多大型Java应用程序已成为内存瓶颈。测量表明,在这些类型的应用程序中,大约25%的Java堆活动数据集被String对象占用。此外,这些String对象中大约有一半是重复项,其中重复项表示string1.equals(string2)为true。从本质上讲,在堆上具有重复的String对象只是浪费内存。

[...]

实际的预期收益最终将减少约10%的堆。请注意,此数字是根据广泛应用计算得出的平均值。特定应用程序的堆减少量可能上下波动很大。


5
@Joe您必须问JVM设计者-字符串实习已经存在了很长时间,而且我怀疑随着JVM /垃圾收集器的性能变得更好,并且随着每台设备的CPU数量的增加,他们可以改善某些情况。过去会带来太多的开销。
assylias,2015年

6
在较旧的版本中,aString可以使用int偏移量和长度来引用数组中的范围。在那种情况下,重复数据删除会更加复杂,但是另一方面,String.substring由于这些子字符串引用了原始数组,因此不需要重复数据。Java 7中的情况已发生变化,从而增加了对重复数据删除功能的需求。
Holger 2015年

3
在字符串中查找子字符串要慢得多(O(n ^ 2)?),而查找两个字符串是否相等则是O(n)最坏的情况,而当两个字符串具有不同的(缓存的)哈希码时即为O(1),即大多数在时间上,这是一个简单的int比较。
assylias,2015年

3
@Joe我添加了一个链接,该链接可能会更好地回答您的问题。
assylias,2015年

4
您的意思是:两个字符串...?我读得很不一样。
Sotirios Delimanolis

67

@assylias答案基本上会告诉您它是如何工作的,这是一个很好的答案。我已经用String Deduplication测试了生产应用程序,并得到了一些结果。Web应用程序大量使用Strings,因此我认为优势非常明显。

要启用字符串重复数据删除,您必须添加以下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分钟后的结果。如您所见,字符串重复数据删除已执行452次,并已“重复数据删除” 801.6 MB字符串。串重复数据删除检查27个000 000字符串。当我比较使用标准Parallel GC的Java 7和使用G1 GC的Java 8u20的内存消耗并启用String Deduplication时,堆的内存下降了大约50%

Java 7并行GC

Java 7并行GC

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

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


1
感谢您的出色回答。但是,您能告诉我您用来测量内存消耗的工具是什么以及如何进行测量吗?指向oracle / java网站的详细说明的任何指针将非常有帮助。我想对我的Web应用程序进行这种分析。在此先感谢:)
nanosoft

3
这些图表来自NetBeans IDE-来自bulit-in探查器。查看Netbeans网站和Google上的教程。或者,您可以从jVisualVM获得相同的图表。
罗伯特·尼斯特罗伊

@RobertNiestroj,根据本文的说法cubrid.org/blog/dev-platform / ...我们不建议/不建议使用G1GC。那么我们如何解决这个问题呢?
Reddy

3
什么问题?如果不能使用G1GC,则不能使用字符串重复数据删除。没有解决方法。
罗伯特·涅斯特罗伊

1
@Reddy,文章指出不应考虑G1,因为它太新了。这是JDK7中的“新官方”。我不确定某些东西是多么的“新”,但是JDK7于2011
问世。– harningt

7

由于您的第一个问题已经回答,因此我将回答您的第二个问题。

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

正如Holger评论中所说,这代表哈希冲突。

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

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

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

这意味着,为了使它们保证相等,必须对每个字符进行比较,以使它们确定两个对象的相等性。它们从比较hashCodes开始而不是使用,equals因为它们使用哈希表作为引用,这提高了性能。


1
这不能回答原始(主要)问题。

1
实际上我没有编辑主要问题...它总是存在,因为您可以看到其他答案。

2
而且他没有回答您的第二个问题,因此我添加了一些信息,希望对阅读该信息的人有所帮助。
mbomb007

2
我读完了所有内容,但是我已经看到了可接受的答案,所以我只想提供新的信息。
mbomb007'1

3
谢谢...您能编辑您的答案并添加一个声明,这是第二个问题的答案...我认为这将对以后的读者有所帮助。:) PS,+ 1

1

他们描述的策略是简单地在可能的多个equal字符串中重用一个字符串的内部字符数组。如果它们相等,则不需要每个String都有自己的副本。

为了更快速地确定是否2个字符串相等,哈希码作为第一步骤,因为它是一个快速的方法,以确定是否字符串可以是相等的。因此,他们的声明:

一旦找到另一个具有相同哈希码的字符串,便将它们逐个字符地进行比较

这是为了使一个特定的相等(但速度较慢)相比,一旦可以平等已使用的哈希码来确定。

最后,相等的字符串将共享一个基础char数组。

Java已经有String.intern()很长时间了,或多或少都做同样的事情(即通过对相等的String进行重复数据删除来节省内存)。关于它的新颖之处在于它发生在垃圾收集期间,并且可以从外部进行控制。


您刚刚复制了该示例链接中的内容。

@Joe我在引用他们声明的一部分时,试图解释为什么哈希码与此相关。编辑答案以使其更明显。
geert3

1
如果要引用其他文档的大块,则应使用blockquote样式并提供某种形式的归属。
ssube
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.