我正在研究我最近的一个项目中的内存问题,该项目将数据保留在内存中以进行快速访问,但是应用程序的内存占用量非常大。
该应用程序大量使用CHM(即Concurrenthashmap) ,因此,无需再费脑筋地猜测CHM是问题所在。 我进行了一个内存分析会话,以了解CHM实际占用了多少内存。
我对结果感到惊讶,CHM占用了大约50%的内存。 因此,可以确定CHM是问题所在,它速度很快,但内存效率不高。
为什么CHM这么胖?
- 键/值由Map.Entry对象包装,这为每个对象创建了一个额外的对象。
- 每个段都是一个可重入锁,因此,如果您有很多小的CHM且默认为并发级别,则将有很多锁对象,并且该对象将排在首位。
- 还有更多的州开展家政活动。
上述所有对象都对内存消耗做出了很好的贡献。
我们如何减少内存占用
如果很难减少CHM的内存占用量,我想到的一些可能原因是
- 它必须支持Map的旧界面
- Java映射使用的哈希码冲突技术是封闭哈希 。 封闭式散列基于在冲突时创建链接列表,封闭式散列对于解决问题非常快,但它对CPU缓存不友好,尤其是当节点进入较大的链接列表时。 关于LinkList问题有一篇有趣的文章
因此,我们需要一种内存效率高的替代CHM实现。
CHM版本2
我开始创建具有低内存占用量的CHM版本,目标是尽可能接近数组。 我还使用了替代的哈希码冲突技术来检查性能, Open_addressing有很多选项
我尝试了以下选项:
- 线性探测 –性能并不是那么好,尽管这是最友好的CPU缓存。 需要花费更多的时间来解决问题。
- Double_hashing –性能在可接受的范围内。
让我们测量CHM V2
- 内存占用
在内存方面有很大的收获,CHM比原始数据多花了大约45%+,新实现的LCHM非常类似于Array类型。
- 单线程PUT性能
CHM在PUT测试中的表现胜过新产品,对于100万个项目,新实施的速度要慢50到80毫秒。 50到80 ms并不是明显的延迟,我认为这对延迟要求以秒为单位的应用程序来说是很好的。 如果延迟要求以毫秒/纳秒为单位,则CHM的任何方式都不是一个好的选择。 LCHM性能较慢的原因是哈希冲突技术,双哈希用于解决有代码冲突的问题。
- 并发添加性能
当使用多个线程来写入映射时,新实现的性能稍好。
- 取得成效
与CHM相比,GET的性能略慢。
结论
新的实现在内存测试中表现出色,并且在获取/输出测试中有点慢。 有几件事情可以做,以提高获取/输出的性能,我们看到的所有性能差异都归因于所使用的探测技术。
- 可以改进探测技术,可以使用线性探测技术来获取缓存友好的访问。
- 使探测技术并行化非常容易。
翻译自: https://www.javacodegeeks.com/2013/05/experiment-with-concurrenthashmap.html