具有大量引用字段(数组除外)的对象是否会破坏 Hotspot JVM 的 GC(s) 堆遍历性能?

Do objects with a lot of reference fields (except arrays) devastate Hotspot JVM's GC(s) heap traversal performance?

假设我定义 a class with dozens of reference fields(而不是使用 Object[] 等引用数组),并在应用程序中大量实例化此 class。

Hotspot JVM中垃圾收集器遍历堆计算可达对象时会影响垃圾收集器的性能吗?或者,对于某些 JVM 的内部数据结构或 class 元数据,它可能会导致显着的额外内存消耗?或者,它会以其他方式影响应用程序的效率吗?

这些方面是否特定于 Hotspot 中的每个垃圾收集器算法,或者 Hotspot 机制的那些部分被所有垃圾收集器共享和使用?

让我改一下问题。 "Is it better to have class A or class B, below?"

class A {
  Target[] array;
}

class B {
  Target a, b, c, ..., z;
}

尽管存在通常的可维护性问题...从 VM 的角度来看,考虑到对 class B 的解析引用,它需要一个取消引用才能到达目标字段。而在 class A 中,它需要 两次 次推导,因为我们还需要读取数组。

两种情况下对象引用的处理略有不同:在class A 中,VM 知道有一个连续的引用数组,因此它不需要知道任何其他信息。在 class B 中,VM 必须知道哪些字段是引用(因为可能有 non-reference 字段,例如),这需要在 class 元数据中维护 oop 映射:

//  InstanceKlass embedded field layout (after declared fields):
...
//    [EMBEDDED nonstatic oop-map blocks] size in words = nonstatic_oop_map_size
//      The embedded nonstatic oop-map blocks are short pairs (offset, length)
//      indicating where oops are located in instances of this 

请注意,虽然存在占用空间开销,但它不太重要,除非你有很多 class 这种奇怪形状的东西,但即便如此,成本也会 per-class,不是 per-instance.

Oop-maps 是在 class 解析期间由共享运行时代码构建的。为特定对象走 "oop"-s 的访问者会查看那些 oop-maps 以找到引用的偏移量,并且该代码也是共享运行时的一部分。因此,此开销与 GC 实现无关。

性能注意事项:

  1. Oop-maps 被分块:相邻引用字段的运行将形成一个连续的 oop-map 块,该块的访问方式与我们在引用数组中使用连续 oop 块时的访问方式非常相似。
  2. GC(标记)性能取决于它必须遵循的引用数量,取消引用的内存延迟将是 first-order 效果。注意,在classA中,我们要遍历更多的引用
  3. null-checks 和数组边界检查在 class 情况下可能很重要,如果请求的索引不是常量并且数组长度在关键代码路径上未知。相比之下,字段是静态绑定的,它们的偏移量总是已知的。

因此,询问 GC/runtime 处理单独字段与数组的区别可能意义不大。照顾参考地点很可能会带来更大的收益。这将规模扩大到 class B,并伴随着相关的可维护性开销——就像很多性能技巧一样。