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

leventov :

Imagine that I define a class with dozens of reference fields (instead of using reference arrays such as Object[]), and instantiate this class pretty heavily in an application.

Is it going to affect the performance of garbage collector in Hotspot JVM, when it traverses the heap to calculate reachable objects? Or, maybe, it would lead to significant extra memory consumption, for some JVM's internal data structures or class metadata? Or, is it going to affect the efficiency of an application in some other way?

Are those aspects specific to each garbage collector algorithm in Hotspot, or those parts of Hotspot's mechanics are shared and used by all garbage collectors alike?

Aleksey Shipilev :

Let me rephrase the question. "Is it better to have class A or class B, below?"

class A {
  Target[] array;
}

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

The usual maintainability issues notwithstanding... From VM side of view, given the resolved reference to class B, it requires one dereference to reach Target field. While in class A, it requires two derferences, because we also need to read through the array.

The handling of object references in two cases is subtly different: in class A, VM knows there is an contiguous array of references, and so it does not need to know anything else. In class B, VM has to know which fields are references (because there could be non-reference fields, for example), which requires maintaining the oop maps in the class metadata:

//  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 

Note that while footprint overhead is there, it is unlikely to matter very much, unless you have lots of classes of this weird shape, but even then the cost would be per-class, not per-instance.

Oop-maps are built during class parsing, by the shared runtime code. The visitors that walk the "oop"-s for the particular object looks into those oop-maps to find the offsets for references, and that code is also the part of shared runtime. So, this overhead is independent of GC implementation.

Considerations for performance:

  1. Oop-maps are chunked: the runs of adjacent reference fields would form a continuous oop-map block that would be visited pretty much like we would with continuous oop block in reference array.
  2. The GC (marking) performance is dependent on the number of references it has to follow, and memory latency on dereferences would be the first-order effect. Note that in class A, we have to traverse more references.
  3. The null-checks and array bounds checks would probably matter in class A case, if requested indices are not constant and array lengths are not known on critical code paths. In comparison, fields are bound statically, and their offsets are always known.

So, it probably makes little sense to ask about the difference in GC/runtime handling of separate fields vs arrays. Taking care of locality of reference quite probably gives a bigger bang for the buck. Which tips the scale to class B, with associated maintainability overheads -- as quite a few performance tricks do.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=434824&siteId=1