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

lev*_*tov 13 java performance garbage-collection jvm jvm-hotspot

想象一下,我定义了一个包含许多引用字段的类(而不是使用引用数组Object[]),并在应用程序中大量实例化该类.

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

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

Ale*_*lev 14

让我重新解释一下这个问题."下面有A级或B级更好吗?"

class A {
  Target[] array;
}

class B {
  Target a, b, c, ..., z;
}
Run Code Online (Sandbox Code Playgroud)

尽管存在通常的可维护性问题......从VM的角度来看,给定已解析的对B类的引用,它需要一个取消引用才能到达Target字段.在A类中,它需要两个差异,因为我们还需要读取数组.

在两种情况下对象引用的处理略有不同:在类A中,VM知道存在连续的引用数组,因此它不需要知道任何其他内容.在B类中,VM必须知道哪些字段是引用(例如,因为可能存在非引用字段),这需要在类元数据中维护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 
Run Code Online (Sandbox Code Playgroud)

请注意,虽然有足迹开销,但它不太重要,除非你有很多这种奇怪形状的类,但即便如此,成本也是每个类,而不是每个实例.

Oop-map是在类解析期间通过共享运行时代码构建的.遍历特定对象的"oop"-s的访问者查看那些oop-map以查找引用的偏移量,并且该代码也是共享运行时的一部分.因此,此开销与GC实现无关.

绩效考虑因素:

  1. Oop-map是分块的:相邻参考字段的运行将形成一个连续的oop-map块,可以像访问参考数组中的连续oop块一样进行访问.
  2. GC(标记)性能取决于它必须遵循的引用数量,并且取消引用的内存延迟将是一阶效应.请注意,在A类中,我们必须遍历更多引用.
  3. 如果请求的索引不是常量且在关键代码路径上不知道数组长度,则在A类情况下,空检查和数组边界检查可能很重要.相比之下,字段是静态绑定的,并且它们的偏移量始终是已知的.

因此,询问单独字段与数组的GC /运行时处理的差异可能没什么意义.照顾参考地点很可能会带来更大的收益.这会将规模提升到B类,并带来相关的可维护性开销 - 正如相当多的性能技巧所做的那样.