JEP 304: Garbage-Collector Interface

OwnerRoman Kennke
Created2016/08/06 08:45
Updated2017/10/16 18:59
TypeFeature
StatusCandidate
Componenthotspot / gc
ScopeImplementation
Discussionhotspot dash gc dash dev at openjdk dot java dot net
EffortL
DurationM
Priority4
Issue8163329

Summary

Improve the build-time isolation of different garbage collectors by designing a clean GC interface.

Goals

Non-Goals

Success Metrics

The implementation is considered a success if GC implementations are completely contained within source files in their src/share/vm/gc/$NAME and maybe src/cpu/share/vm/gc/$NAME directories. No code outside of those directories should include files from within those directories, nor should there be any GC-specific if-else branches. Code for one GC implementation must only depend on code in src/share/vm/gc/shared and its own subdirectory.

Performance must not be negatively impacted.

Motivation

Currently, each garbage collector implementation consists of source files inside their src/share/vm/gc/$NAME directories, e.g. G1 is in src/share/vm/gc/g1, CMS in src/share/vm/gc/cms, etc. However, there are bits and pieces scattered all over the HotSpot sources. For example, most GCs require certain barriers, which need to be implemented in the runtime, interpreter, C1 and C2. Those are not implemented by the GC that require them, but ususally in place, guarded by if-else-chains testing for specific GC implementations currently in use. Likewise, there are other small pieces of code in various places that are treated like this.

This has several disadvantages.

  1. For GC implementors, implementing a new garbage collector requires knowledge about all those various places, and how to extend them for their specific needs.
  2. It is difficult to exclude, at build time, specific garbage collector(s). This has recently come up with the proposal to deprecate the CMS collector (JEP 291). Similarily, the INCLUDE_ALL_GCS has long been a way to build the JVM with only the serial collector built-in.

A cleaner GC interface would make it much easier to implement new collectors, it would make the code much cleaner, and simpler to exclude one or several collectors at build time.

Adding a new garbage collector should be a matter of implementing a well documented set of interfaces, rather than figuring out all the places in the runtime, interpreter, C1 and C2 that needs changing. A new garbage collector may require new interfaces (like for example, Shenandoah requires read- and write barriers, even for primitive type memory access). We acknowledge that there is no one GC algorithm which suits everyone's needs. Also, new memory architectures may require new/changed interfaces. The GC interfaces should allow for relatively easy extensions.

The interface should be designed with JVMCI in mind. It should be clear to an implementation of JVMCI what to implement and how in order to support which GC(s).

Last but not least, a clean GC interface would help tremendously with inclusion of new GCs such as Shenandoah (JEP 189).

Description

The idea is that the GC interface would be defined by a single class named GCInterface. Every collector needs to implement that. The GCInterface class would drive all aspects of interaction between the garbage collector and the rest of HotSpot:

During JIT and interpreter compilation, the barriers for the interpreter and C1 and C2 compilers would be generated by the GC, through a virtual call, e.g. oopDesc::bs()->c1_support()->pre_barrier(...).

For implementations that are shared, the corresponding code should exist in a helper class, so that it can easily be called by the various GC implemenations that need it. For example, there could be a helper class that implements the various barriers for card table support, and any GC that requires card table post-barriers would call the corresponding methods of that CardTable helper class. This way the interface provides flexibility to implement completely new barriers, and at the same time allows for reuse of existing code in a mix-and-match style. It needs to be determined yet on where such helper code should be kept. E.g. for C2-specific barrier code, should it be in src/share/vm/gc/shared or src/share/vm/opto or maybe some subdirectory-combination like src/share/vm/gc/shared/c2, or even a separate directory altogether?

Example

To give one example to illustrate one possible approach:

klass_oop_store() in klass.cpp

void Klass::klass_oop_store(oop* p, oop v) {
  assert(!Universe::heap()->is_in_reserved((void*)p), "Should store pointer into metadata    ");
  assert(v == NULL || Universe::heap()->is_in_reserved((void*)v), "Should store pointer t    o an object");

  // do the store
  if (always_do_update_barrier) { 
    klass_oop_store((volatile oop*)p, v);
  } else {
    klass_update_barrier_set_pre(p, v);
    *p = v;
    klass_update_barrier_set(v);
  }
}
 
void Klass::klass_oop_store(volatile oop* p, oop v) {
  assert(!Universe::heap()->is_in_reserved((void*)p), "Should store pointer into metadata    ");
  assert(v == NULL || Universe::heap()->is_in_reserved((void*)v), "Should store pointer t    o an object");

  klass_update_barrier_set_pre((oop*)p, v); // Cast away volatile.
  OrderAccess::release_store_ptr(p, v);
  klass_update_barrier_set(v);
}

always_do_update_barrier is only going to be true for CMS currently.

With the GCInterface there would be a call into GC specific code that would do the actual store, something like

void Klass::klass_oop_store(oop* p, oop v) {
  // The following should actually be done statically once, somewhere else.
  // It's only here to demonstrate the mechanics of the GC interface.
  `GCInterface`* gci = `GCInterface`::get_interface();
  BarrierSet* bs = gci->barrier_set();
  bs->oop_store(p, v);
}

For GC's that need the memory barrier

GCBarrierSet::_oop_store(oop* p, oop v) {
  pre_barrier(p, v);  // Non-virtual
  OrderAccess::release_store_ptr(p, v);
  post_barrier(p, v); // Non-virtual
}

Otherwise

GCBarrierSet::oop_store(oop* p, oop v) {
  pre_barrier(p, v);  // Non-virtual
  *p = v;
  post_barrier(p, v); // Non-virtual
}

This way we'd reduce the number of virtual calls to 1. Plus, GCs that don't actually need a pre- or post-barrier (e.g. Shenandoah doesn't require post-barriers) can leave them out altogether. And as an aside, the above interface would actually belong into BarrierSet, and thus get called through the cached BarrierSet instance as for the current pre-barrier and post-barrier calls.

And since oop_store() in oop.inline.hpp basically does the same, it could reuse the exact same GC interface.

Alternatives

The current GC interface consists of:

We could work from there, and extend them to achieve separation. However, it has several problems:

I am leaning to make a new GCInterface, that would be implemented by each GC, and provides a CollectorPolicy, CollectedHeap, and BarrierSet, plus any new GC interface related things that are needed (e.g. GCServicabilitySupport, etc.).

Testing

This is purely refactoring. Everything that worked before needs to work afterwards.

Risks and Assumptions

A GC interface adds complexity. Currently stable and tested code in all collectors need to be changed. We hope that the benefit of clean and strict interface between GCs and the rest of HotSpot outweights the cost and risk of changes.

We may not be able to implement it without harming performance.

We may fail to identify and/or isolate all the touch points between GC and rest of the JVM.

A first prototype would be implemented by focusing on code that directly includes gc/cms files, and abstract that out into GC interface code. This will allow us to identify obvious problems early on and address them. I suggest to integrate the majority of changes required for the GC interface at once, in order to get a fairly uniform treatment of all cases. Interpreter, C1, and C2 could be addressed in additional steps.

A current snapshot of the work-in-progress can be found here.

Dependences

This JEP will help with JEP 291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector, because it provides a way to isolate it, and allow it to be maintained by others if needed.

This JEP will also help with JEP 189: Shenandoah: An Ultra-Low-Pause-Time Garbage Collector, and make its changes less intrusive. The GCInterface needs to be extended to account for Shenandoah's read- and write-barriers though. Shenandoah requires read- and write-barriers in BarrierSet (for each of: runtime, interpreter, c1 and c2) plus the corresponding hooks all over the runtime, interpreter, c1 and c2. This change is very significant and would need to be discussed as part of the Shenandoah JEP. We added methods read_barrier() and write_barrier() and obj_equals() to BarrierSet, as well as all the corresponding hooks in the runtime, plus equivalent code generation in the interpreter, C1 and C2.