While writing the JDK 19 update blog post I strongly considered writing about the “new” (jdk.vm.internal.FillerObject and jdk.vm.internal.FillerArray) objects that may appear in heap dumps. I thought they were not that interesting, but it looks like people quickly noticed them. This post describes their purpose after all. Update 2024/07/22: The name of the latter class is [Ljdk/internal/vm/FillerElement; beginning with JDK 21.

Background

In Hotspot there is a fairly interesting property of Java heaps we call parsability. It means that one can parse (linearly walk) the parts of the heap (whether these are called regions, spaces, or generations depending on the collector) from their respective bottom to their top addresses. The garbage collector guarantees that after an object in the Java heap another valid Java object of some kind always follows.

Every object header contains information about the type of the object (i.e. class) which allows inference of the size of that object.

This property is exploited in various ways. This is a non-exhaustive list:

  • heap statistics (jmap -histo): just start at the bottom of all parts of the heap, walk to its top, and collect statistics.
  • heap dumps
  • some collectors require heap parsability for at least parts of the heap when they partially collect the heap in young collections: they use an inexact remembered set to record approximate locations where there are interesting references. These recorded areas (remembered set entries) need to walked quickly during garbage collection. This post explains how this works in a bit more detail in the introduction.

So how could the Java heap get unparsable? After all objects are allocated in a linear fashion already?

There are two (maybe more) causes:

  • the first is LAB allocation: threads do not synchronize with others for all memory allocations, but they carve out larger buffers that they use for local allocation without synchronization to others. These LABs are fixed size - if a thread can’t fit an allocation into its current LAB any more, the remainder area needs to be formatted properly before getting a new LAB.
  • the second is class unloading. Class unloading makes objects which classes were unloaded unparsable as their class information will be discarded. Some collectors avoid this issue by compacting the heap after class unloading to remove all these invalid objects from the parsable Java heap area.

Before JDK 19 the Hotspot VM used instances of java.lang.Object and integer arrays ([I) to reformat (“fill”) these holes in the heap. The former are used only for the tiniest holes to fill, while everything else uses integer arrays.

I.e. in a complete heap histogram or heap dump that included non-live data you may have noticed an abundance of java.lang.Object and [I instances that were not referenced from anywhere in your program.

Here is an example jmap -histo <pid> run of some program with an older JVM:

 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:         16015      350913960  [B (java.base@11.0.16)
   2:        467918       33690096  java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask (java.base@11.0.16)
   3:          4559       18664488  [I (java.base@11.0.16)
   4:             1        2396432  [Ljava.util.concurrent.RunnableScheduledFuture; (java.base@11.0.16)
   5:         10715         342880  java.util.concurrent.SynchronousQueue$TransferStack$SNode (java.base@11.0.16)
   6:         10528         336896  java.util.concurrent.locks.AbstractQueuedSynchronizer$Node (java.base@11.0.16)
   7:         10149         243576  java.lang.String (java.base@11.0.16)
[...]

A large part of the [I instances are likely filler objects.

The Change

JDK-8284435 added special, explicit filler objects with the names jdk.vm.internal.FillerObject and Ljdk.vm.internal.FillerArray; in Java heap histograms (Actually, in the original change there has been a typo in the filler array type name, it has errorneously been called Ljava.vm.internal.FillerArray;, fixed in JDK-8294000).

Update 2024/07/22: There has been another renaming of the “array” filler to [Ljdk/internal/vm/FillerElement; to conform to Java’s array class name notation as of JDK-8319548 and backported to JDK 21.

These classes are created up front at VM startup similar to most VM internal classes.

These serve the same purpose as java.lang.Object and [I instances for filling holes, but have the added advantage to us Hotspot VM developers that if we see them referenced in crash logs, or references into these kinds of objects, there is a high likelihood of some bug related to dangling references to garbage objects. It’s not fool-proof, but makes crash investigation a bit easier as we can now more easily distinguish between dangling references and legitimate references to instances of the previous kinds of filler objects. Heap verification can now also better distinguish between filler and live objects independent of the garbage collector.

Here is another run of jmap -histo of the same program as above with a current VM:

 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:         10740      152377136  [B (java.base@20-internal)
   2:        250078       18005616  java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask (java.base@20-internal)
   3:          1699       14286176  Ljdk.internal.vm.FillerArray; (java.base@20-internal)
   4:             1        1065104  [Ljava.util.concurrent.RunnableScheduledFuture; (java.base@20-internal)
[...]
  23:           361          11552  java.lang.module.ModuleDescriptor$Exports (java.base@20-internal)
  24:           127          11264  [I (java.base@20-internal)
  25:           274          10960  java.lang.invoke.MethodType (java.base@20-internal)
[...]
  76:            11           1056  java.lang.reflect.Method (java.base@20-internal)
  77:            66           1056  jdk.internal.vm.FillerObject (java.base@20-internal)
  78:             1           1048  [Ljava.lang.Integer; (java.base@20-internal)
[...]

Notice that in this histogram, there are quite a few Ljdk.internal.vm.FillerArray; instances and much less [I instances (these histograms were taken randomly, so they are not directly comparable). jdk.internal.vm.FillerObject instances are rather rare because holes with minimal size are rare.

(On 64 bit machines they only show up when not using compressed class pointers via -XX:-UseCompressedClassPointers as otherwise filler arrays also fit minimum instance size).

Obviously the names and everything related are Hotspot VM internal details and can change at any notice.

Impact Discussion

For the end user there should be no difference apart from instances of these objects showing up in complete heap dumps.

That’s all for today, mystery solved :),

Thomas