JVM空间复杂性细节:单链表与双链表



我遇到了一个奇怪的情况,学生(我在这里是一名助教)必须实现他们自己版本的Singly Linked List(SLL),并根据经验将其与Java标准库实现的Doubly Linked List进行比较。

这就是奇怪的地方:我看到多名学生注意到,与包含相同类型元素的相同数量的SLL相比,DLL文件的额外空间利用率约为0.5%。一直以来,数据结构的基本分析告诉我,SLL每个节点有2个引用(1个到下一个元素,1个到包含的值),而DLL有3个引用(对前一个元素的额外一个引用)。换句话说,这意味着每个节点的空间使用量增加了50%(不考虑包含值的大小)。

包含的值大多是Integer值对象,所以我认为包含的值的大小在这里不太重要。

是什么导致了这种2个数量级的差异?我不完全确定"JVM/collectionslibraries优化"是否能涵盖全部差异;否则,它将是一个地狱般的JVM/javastdlib优化。

对于具有32位引用(压缩oops)的64位JVM,在Oracle JVM/OpenJDK上使用的空间应该相同

对于具有两个参考的节点

header: 12 bytes
two references: 8 bytes
alignment padding: 4 bytes

由于默认情况下所有对象都以8字节的偏移量对齐,因此每个节点的总数为24字节。

对于具有三个参考的节点

header: 12 bytes
three references: 12 bytes
alignment padding: 0 bytes

总数再次为24字节。

真正的问题是,你为什么会看到任何不同。这很可能是由于内存核算不准确造成的。

JVM使用TLAB(线程本地分配缓冲区)。这允许JVM中的线程获取内存块,并从这些块中并行分配。不利的一面是,你只能从公共伊甸园空间中看到有多少内存被使用,也就是说,你不知道每个区块有多少被使用。

解决这个问题的一个简单方法是关闭TLAB,它为您提供逐字节的内存帐户(以牺牲一些性能为代价)

例如,在命令行中尝试-XX:-UseTLAB以禁用TLAB,您将看到分配的每个对象的大小。

很难理解为什么有任何差异。

首先要注意的是,Java对象在其头的形式上有很大的开销。这就降低了你50%的期望值。

接下来,当您考虑到引用通常是4字节宽(给定64位HotSpot上的压缩OOP),但内存总是以大小可被8整除的块分配时,您可以看到一个结构末尾未使用的4字节变成了DLL示例中的第三个引用。

除了Marko所说的每个链表节点对象的内存开销之外,存储在这些节点中的"整数值对象"可能没有你想象的那么小。java的DLL的元素类型是一个泛型参数,java中的泛型参数始终是对象(而不是基元),因此即使您可能要将ints添加到java的DLL中,它们也会转换为对象(请参阅装箱/取消装箱)并存储为对象。

如果你的学生的SLL存储了实际的基元ints,那么我实际上希望他们的类比Java的DLL占用的空间小得多。如果你的学生存储Integer对象,那么你应该考虑这样一个事实,即这些对象所占据的空间会进一步稀释两个类所占据的预期空间之间的差异。

相关内容

  • 没有找到相关文章

最新更新