In order for Scavenger to find all live objects, it uses multiple threads and parallelizes the work as it traverses through the object trees. The number of working threads is dependent on the system: systems with more threads can use more threads to work on highly parallelized tasks. More information about this topic can be found here.
However, not all tasks are parallelizable, and so they cannot benefit from using more threads as there is simply not enough work, resulting in many threads being idle. This becomes a problem since Scavenger has multiple synchronization steps, where it waits for all working threads before continuing. By having many idle working threads, the synchronization step slows down without improving any other Scavenger process, thus having extra working threads becomes detrimental to the GC cycle.
Using Dynamic Threads
In order to take into consideration that some cycles aren’t parallelizable while others are, a static number of working threads is not optimal. For every cycle, the advised number of threads is calculated based on the actual number of observed working threads and the historic evidence observed from previous cycles. The amount of time spent on each cycle is used to weigh the amount of influence the cycle has on the final outputted number of threads to use.
A new variable, dynamicThreadsHeuristicBooster, was introduced to increase the number of threads to make the system use as many threads as possible while retaining a low amount of idle threads. This variable represents the percentage at which the number of threads grows, defaulting to 20%. The variable is configurable through the command line option -XXgc:dynamicThreadsHeuristicBooster=X, where X is an integer representing how aggressive the heuristic will try to grow the number of threads it’ll use.
A debugging tracepoint, ID being j9mm.704, was also introduced to allow debugging of this heuristic.
For more information about OpenJ9 GC and its policies, please take a look at the online OpenJ9 GC documentation.