【发布时间】:2017-02-03 20:54:52
【问题描述】:
AbstractConcurrentMap 是 Groovy 中的核心类,用于存储在运行时添加到 Groovy 类的动态属性。我将 Grails 2.1.2 与 Groovy 1.8.8 一起使用,但我认为该问题存在于所有 Groovy 版本中(链接的源代码适用于 Groovy 版本 2.4.3)。
问题发生在内部类Segment的put()方法(第105行):
当当前计数大于映射的阈值时,会发生rehash()。现在棘手的部分是,Map 包含对对象的软引用,rehash() 验证这些引用。因此,当 GC 丢弃软引用时,生成的段不会扩展(正如 put() 方法中所假设的那样)。
-
在last line of rehash() 中,Segmen't 内部计数器已更新
李>count = newCount(这是“活动”未丢弃引用的数量,并且可以小于之前的计数,如上所述) rehash() 完成后,put() 方法继续,但有问题的部分是,它忽略了内部
count的先前设置,并且在每种情况下都设置了先前的 count+1 值在线124、143 和159
所以下面的步骤正在发生:
- 地图状态:
threshold = 786432; count=786432 - 新元素被插入到地图中:
count = 786433; threshold = 786432 - 因为新计数将大于阈值rehash() 发生
- rehash() 发现大多数对象都被垃圾回收,因此它不会增加 Segment 的大小,但无论如何它会将所有对象从一个表复制到另一个表 (System.arrayCopy())。
- rehash() 将内部计数设置为新值,该值更小,因为许多对象被垃圾收集(软引用),可以说:
count = 486 000 -
put() 继续,忽略
count = 486 000并将计数设置为count = 786433 - 插入了另一个元素,但在此状态下,计数仍大于阈值,因此重新哈希再次发生
- 从现在开始,添加到地图的每个元素都会触发rehash(),这会对性能产生巨大影响。
当这种情况发生在多线程环境中时,所有其他线程都在等待(停放)lock(),直到 rehash() 和 put() 完成(然后下一个线程再次执行 rehash())。你可以想象这是什么性能影响......
我不明白这个错误是如何在这么多版本中存活下来的,尽管该类被广泛使用,但没有人注意到。也许我错过了什么?
建议的解决方案:
在 rehash 完成后更新 c 变量。 在第 105 和 106 行之间添加:
c = count + 1
【问题讨论】:
-
您应该在issues.apache.org/jira/browse/GROOVY 报告此问题,并将您提出的修复作为差异/拉取请求,并可能进行一些测试以显示问题。至少您将针对 groovy 的专家。 SO 几乎不是潜在语言错误的同行评审页面。
-
感谢回复,我已经提交了问题:issues.apache.org/jira/browse/GROOVY-7448
标签: groovy