Java 8的string重复数据删除function

由于Java中的String (与其他语言一样)消耗大量内存,因为每个字符都占用两个字节,所以Java 8引入了一个名为string重复数据删除的新function,它利用了字符数组在string和最终内部这一事实,所以JVM可以搞砸他们。

我已经阅读了这个例子 ,但由于我不是一个亲java编码器,我很难理解这个概念。

这是它所说的,

已经考虑了string复制的各种策略,但现在实现的策略遵循以下方法:垃圾回收器访问String对象时,会注意到char数组。 它取得它们的散列值,并将其与一个对数组的弱引用并列存储。 只要它发现另一个具有相同散列码的string,它们会将它们按字符比较。 如果它们匹配,则会修改一个string并指向第二个string的字符数组。 然后第一个字符数组不再被引用,可以被垃圾收集。

这整个过程当然会带来一些开销,但受到严格的限制。 例如,如果一个string没有被发现重复一段时间,它将不再被检查。

我的第一个问题,

这个话题还没有资源,因为它是最近在Java 8 update 20中添加的,这里有没有人可以分享一些实际的例子来说明如何减lessString在Java中消耗的内存呢?

编辑:

上面的链接说,

只要它发现另一个具有相同散列码的string,它们会将它们按字符比较

我的第二个问题,

如果两个String哈希码相同,那么Strings已经是相同的,那么为什么一旦发现两个String具有相同的哈希码,为什么要比较它们?

想象一下,你有一个电话簿,其中包含人,其中有一个String firstNameString lastName 。 而在你的电话簿中,有十万人拥有相同的firstName = "John"

因为您从数据库或文件中获取数据,所以这些string不会被拦截,所以您的JVM内存中包含char数组{'J', 'o', 'h', 'n'} 10万次,每个Johnstring一个。 这些数组中的每一个都需要20个字节的内存,所以这些100k的Johns占用2MB的内存。

使用重复数据删除技术,JVM将意识到“John”会被重复多次,并使所有Johnstring指向相同的底层字符数组,从而将内存使用量从2MB减less到20个字节。

你可以在JEP中find更详细的解释。 尤其是:

许多大规模的Java应用程序目前都是内存瓶颈。 测量表明,在这些types的应用程序中设置的大约25%的Java堆实时数据被String对象占用。 而且,大约一半的String对象是重复的,其中重复的意思是string1.equals(string2)是真的。 在堆上有重复的String对象本质上就是浪费内存。

[…]

预计的实际效益最终会降低10%左右。 请注意,这个数字是基于广泛应用的计算平均值。 特定应用程序的堆栈减less量可能会有很大的变化。

@assylias答案basiclly告诉你它是如何工作,是非常好的答案。 我已经使用string重复数据删除testing了一个生产应用程序,并有一些结果 networking应用大量使用string,所以我认为这个优势非常明显。

要启用string重复数据删除,您必须添加这些JVM参数(您至less需要Java 8u20):

 -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics 

最后一个是可选的,但就像名称所示,它显示了string重复数据删除统计信息。 这是我的:

 [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重复数据删除被执行452次,“重复数据删除” 801.6 MBstring。 string重复删除检查了2700万个string。 当我比较从Java 7的内存消耗与标准的并行GC到Java 8u20与G1 GC和启用string重复数据删除堆下降接近50%

Java 7并行GC

Java 7并行GC

带string重复数据删除的Java 8 G1 GC

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

既然你的第一个问题已经被回答了,我会回答你的第二个问题。

String对象必须逐字比较,因为虽然相等的Object意味着相等的哈希,但反过来并不一定是真的。

正如Holger在他的评论中所说的那样,这代表了哈希碰撞。

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

  • 如果两个对象按照equals(Object)方法equals(Object) ,那么在两个对象的每一个上调用hashCode方法必须产生相同的整数结果。

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

这意味着为了保证平等,每个人物的比较是必要的,以便他们确认这两个物体的平等。 他们从比较hashCode开始,而不是使用equals因为它们使用哈希表作为引用,这会提高性能。

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

为了更快地确定2个string是否相等,散列码被用作第一步,因为这是判断string是否相等的一种快速方法。 因此他们的发言:

只要它发现另一个具有相同散列码的string,它们会将它们按字符比较

这是为了使一定的 (但较慢)比较相等,一旦可能的平等已经确定使用散列码。

最后,相等的string将共享一个底层字符数组。

Java有String.intern()很长一段时间,做或多或less相同(即通过去重等同的string来节省内存)。 这个新颖的东西是在垃圾收集的时候发生,可以外部控制。