Groovy 抽象 ConcurrentMap 中的错误?
Bug in Groovy AbstractConcurrentMap?
AbstractConcurrentMap是Groovy中的核心class,用于存储运行时添加到Groovyclasses中的动态属性。我将 Grails 2.1.2 与 Groovy 1.8.8 一起使用,但我认为该问题存在于所有 Groovy 版本中(链接的源代码适用于 Groovy 版本 2.4.3) .
问题发生在内部 class 段的 put() 方法(第 105 行):
当当前计数大于地图的阈值时 rehash() happens. Now the tricky part is, that the Map holds soft references to objects and rehash() validates those references. So when GC discards soft references, the resulting segment doesn't expand (as it is assumed in the put() 方法)。
在last line of rehash()中更新了Segmen的内部计数器count = newCount
(这是"alive"未丢弃引用的数量,可以小于之前的计数如上所述)
在rehash() is done, the put() method continues, however the buggy part is, that it disregards the previous setting of the internal count
and it sets the previous count+1 value in every case on lines 124, 143 and 159
之后
因此发生了以下步骤:
- 地图状态:
threshold = 786432; count=786432
- 新元素已插入地图:
count = 786433; threshold = 786432
- 因为新计数会大于阈值 rehash() 发生了
- rehash() 发现,大多数对象都被垃圾收集了,因此它不会增加段的大小,但是无论如何它将所有对象从一个 table 复制到另一个(System.arrayCopy()).
- rehash() 将内部计数设置为新值,该值更小,因为许多对象被垃圾回收(软引用),比方说:
count = 486 000
- put() 继续,忽略
count = 486 000
并将计数设置为 count = 786433
- 插入了另一个元素,但在此状态下,计数仍然大于阈值,因此重新哈希再次发生
- 从现在开始,添加到地图的每个元素都会触发 rehash(),这会对性能产生巨大影响。
在多线程环境中发生这种情况时,所有其他线程都在等待(停放)lock(),直到 rehash() 和 put() 完成(然后下一个线程再次执行 rehash()) .你可以想象这是什么性能影响...
我不明白尽管 class 被广泛使用,但这个错误如何能够在这么多版本中幸存下来而没有人注意到。也许我遗漏了什么?
建议解决方案:
重新哈希完成后更新 c 变量。
在第 105 行和第 106 行之间添加:
c = count + 1
此错误已在 Groovy JIRA https://issues.apache.org/jira/browse/GROOVY-7448 上报告,现已修复。
Fix Version/s:
2.4.4, 2.5.0-beta-1
AbstractConcurrentMap是Groovy中的核心class,用于存储运行时添加到Groovyclasses中的动态属性。我将 Grails 2.1.2 与 Groovy 1.8.8 一起使用,但我认为该问题存在于所有 Groovy 版本中(链接的源代码适用于 Groovy 版本 2.4.3) .
问题发生在内部 class 段的 put() 方法(第 105 行):
当当前计数大于地图的阈值时 rehash() happens. Now the tricky part is, that the Map holds soft references to objects and rehash() validates those references. So when GC discards soft references, the resulting segment doesn't expand (as it is assumed in the put() 方法)。
在last line of rehash()中更新了Segmen的内部计数器
count = newCount
(这是"alive"未丢弃引用的数量,可以小于之前的计数如上所述)在rehash() is done, the put() method continues, however the buggy part is, that it disregards the previous setting of the internal
count
and it sets the previous count+1 value in every case on lines 124, 143 and 159 之后
因此发生了以下步骤:
- 地图状态:
threshold = 786432; count=786432
- 新元素已插入地图:
count = 786433; threshold = 786432
- 因为新计数会大于阈值 rehash() 发生了
- rehash() 发现,大多数对象都被垃圾收集了,因此它不会增加段的大小,但是无论如何它将所有对象从一个 table 复制到另一个(System.arrayCopy()).
- rehash() 将内部计数设置为新值,该值更小,因为许多对象被垃圾回收(软引用),比方说:
count = 486 000
- put() 继续,忽略
count = 486 000
并将计数设置为count = 786433
- 插入了另一个元素,但在此状态下,计数仍然大于阈值,因此重新哈希再次发生
- 从现在开始,添加到地图的每个元素都会触发 rehash(),这会对性能产生巨大影响。
在多线程环境中发生这种情况时,所有其他线程都在等待(停放)lock(),直到 rehash() 和 put() 完成(然后下一个线程再次执行 rehash()) .你可以想象这是什么性能影响...
我不明白尽管 class 被广泛使用,但这个错误如何能够在这么多版本中幸存下来而没有人注意到。也许我遗漏了什么?
建议解决方案:
重新哈希完成后更新 c 变量。 在第 105 行和第 106 行之间添加:
c = count + 1
此错误已在 Groovy JIRA https://issues.apache.org/jira/browse/GROOVY-7448 上报告,现已修复。
Fix Version/s:
2.4.4, 2.5.0-beta-1