Epsilon GC: The Arbitrarily Low Overhead Garbage (Non-)Collector Summary ------------------ Develop the GC that only takes care about the allocation, and does not implement any actual garbage collection strategy. Once available Java heap is exhausted, exit the JVM. Goals ------------------ Provide a completely passive GC implementation with bounded allocation limit, and lowest runtime performance overhead possible. A successful implementation is an isolated code change, not touching other GCs, and having minimal changes in the rest of JVM. Non-Goals ------------------ It is not the goal to introduce manual memory management features in Java language and/or JVM. It is not the goal to introduce new APIs to manage Java heap. It is not the goal to change/cleanup internal JVM interfaces to fit this collector. Motivation ------------------ There are four use cases where such a no-op collector proves useful. First, for performance testing reasons, having the GC that does almost nothing is the useful tool to do differential performance analysis for other real collectors. Having a no-op collector can help to filter out GC-induced performance artifacts. Second, for performance-sensitive applications, where developers are conscientious about memory allocations, and arrive to (almost) completely garbage-free applications. There, the choice of GC ends up in choosing what GC barriers you can tolerate. Most OpenJDK collectors are generational, and therefore emit at least one reference write barrier. Avoiding this barrier brings the last bit of performance improvement. Also, in those applications, exiting and restarting the JVM -- letting load balancers figure out failover -- is sometimes a saner recovery strategy than accepting a GC cycle. Third, for OpenJDK testing, having a way to establish a threshold for allocated memory is useful to assert memory pressure invariants. Today, we have to pick up the allocation data from MXBeans, or even resort to GC logs parsing. Having a GC that accepts only the bounded number of allocations, and exits on failure simplifies testing. Fourth, for VM development purposes, it helps to understand the absolute minimum required from VM-GC interface to have a functional allocator. This serves as the proof that VM-GC interface is sane, which is important in lieu of JEP-8163329 ("GC interface"). Description ------------------ Epsilon GC works by implementing TLAB allocation, and issuing TLABs in a contiguous chunk of allocated heap. This allows lock-free within-TLAB allocation handled by existing VM code, and trivial lock-free TLAB issuance code in the collector itself. Issuing TLABs also helps to keep the committed memory taken by a process bounded by what had been actually allocated. Humongous out-of-TLAB allocations are handled by exactly the same code. The barrier set used by Epsilon is completely empty/no-op, because it does not do any GC cycles, and therefore does not care about the object graph, object marking, object copying, etc. Since the only part of runtime interface where Epsilon is taking part of is issuing TLABs, its performance largely depends on the TLAB sizes issued. With arbitrarily large TLABs and arbitrarily large heap, the performance overhead can be described by an arbitrarily low positive value, hence the name. Once Java heap is exhausted, no TLAB issuance is possible, no recovery action is also possible, and therefore we have to report failure. There are several options at that point, most are in line with what existing collectors do: a) Throw OutOfMemoryError with a descriptive message. b) Perform a heap dump (enabled, as usual, with -XX:+HeapDumpOnOutOfMemoryError). c) Fail the JVM hard and optionally perform the external action (through the usual -XX:OnError=...), e.g. starting the debugger or notifying the external monitoring system about the failure. The prototype implementation can be found at: http://cr.openjdk.java.net/~shade/epsilon/ The initial runs prove the concept by surviving small workloads, and failing predictably on larger ones. http://cr.openjdk.java.net/~shade/epsilon/hello-world.png http://cr.openjdk.java.net/~shade/epsilon/alloc.png Alternatives ------------------ There are no OpenJDK alternatives that disable all GC barriers. If barriers are not the issue, then using Serial or Parallel(Old) GC should fit the same performance profile, assuming we can configure their respective heuristics to never do GCs (i.e. by setting very large young gen size, disabling adaptive heuristics, etc). This is hard to guarantee with the multitude of GC options they provide. Testing ------------------ Common GC tests would not be suitable for Epsilon GC, because most tests assume they can allocate an arbitrary amount of garbage. New tests would need to be developed to test that collector indeed works well on low-allocating workloads, and that it fails on heap exhaustion in a predictable manner. New jtreg tests under hotspot/gc/epsilon would be enough to assert correctness. One-off performance testing during the Epsilon development would be enough to assert the performance characteristics when running with interpreter, C1 and C2 compilers. On-going performance testing is not required since implementation is supposed to be stable since the beginning. Risks and Assumptions ------------------ Usefulness vs. maintenance costs. It can be argued that such an implementation is useless to have in the product, because no one needs it. The experience, however, tells that many players in the Java ecosystem already did this exercise with expunging GC from their custom-built JVMs. That means, having a standard no-op GC option would help that part of the ecosystem. Coupled with low maintenance costs if implementation proves trivial, this risk is minimal. Public expectations. Providing the garbage collection implementation that does not in fact do garbage collection may be seen as the dangerous practice. Accidentally enabling Epsilon collector in production may lead to surprise JVM failures when heap is exhausted. We think this risk is minimal if feature remains under "experimental" flag, that requires -XX:+UnlockExperimentalVMOptions to access. Implementation complexity. It might be the case that implementation would need more changes in the shared code than anticipated, for example in compilers and platform-specific backends. Our prototyping code seems to indicate these changes are isolated enough to be benign. If that proves to be a risk, it should be mitigated by JEP-8163329 ("GC interface"). Dependencies ------------------ This work may depend on JEP-8163329 ("GC Interface") to minimize shared code changes. It is might not require the GC interface, however, if the shared code changes are minimal.