我遇到了一个奇怪的情况,学生(我是这方面的教学助理)必须实现他们自己的单一链接列表(SLL)版本,并根据经验与双标准的Java标准库实现进行比较链接列表。
这就是它变得奇怪的地方:我看到多个学生注意到,与包含相同数量的相同类型元素的SLL相比,DLL配置文件的空间利用率大约为0.5%。对数据结构的所有基本分析告诉我,SLL每个节点有2个引用(1到下一个元素,1到包含的值),而DLL有3个(对前一个元素的另外一个引用)。换句话说,每个节点的空间使用量增加50%(忽略所包含值的大小)。
包含的值主要是整数值对象,所以我不认为包含的值的大小在这里太重要了。
导致 2个数量级差异的原因是什么?我不完全确定“JVM /集合库优化”可以涵盖整体差异;否则它必须是JVM / java std lib优化的一个地狱。
答案 0 :(得分:3)
对于具有32位引用(压缩oops)的64位JVM,Oracle JVM / OpenJDK上使用的空间应该相同
对于具有两个引用的节点
header: 12 bytes
two references: 8 bytes
alignment padding: 4 bytes
每个节点总共24个字节,因为默认情况下所有对象都由8个字节的偏移量对齐。
对于具有三个引用的节点
header: 12 bytes
three references: 12 bytes
alignment padding: 0 bytes
总计再次为24个字节。
真正的问题是你为什么看到任何差异。这很可能是由于内存记帐不准确造成的。
JVM使用TLAB(线程本地分配缓冲区)这允许JVM中的线程获取内存块并从这些块中同时分配。在缺点方面,您只能看到常用的Eden空间使用了多少内存,即您不知道每个块的使用量是多少。
一个简单的方法是关闭TLAB,它为您提供逐字节内存帐户(以某些性能为代价)
i.g。在命令行上尝试-XX:-UseTLAB
以禁用TLAB,您将看到分配的每个对象的大小。
答案 1 :(得分:2)
很难理解为什么
首先请注意,Java对象的头部形式有很大的开销。这会降低你50%的预期。
接下来,当您考虑引用通常为4个字节宽(在64位HotSpot上给定压缩OOP),但该内存始终以大小可被8整除的块分配时,您可以看到剩下的内容因为在一个结构的末尾未使用的4个字节成为DLL示例中的第三个引用。
答案 2 :(得分:2)
除了Marko所说的关于每个链表节点对象的内存开销之外,你的"整数值对象"存储在这些节点中的可能并不像您想象的那么小。 java的DLL的元素类型是一个泛型参数,java中的泛型参数总是对象(永远不是原语),所以即使你可能将int
添加到Java的DLL中,它们被转换为对象(参见装箱/拆箱)并存储为对象。
如果你的学生' SLL存储实际的原始int
,然后我实际上期望它们的类占用比Java的DLL少得多的空间。如果你的学生存储Integer
个对象,那么你应该考虑这些对象所占用的空间进一步淡化了这两个类占用的预期空间之间存在的差异。