Search code examples
performancerustjvmgarbage-collection

Why can't an "active" garbage collector work like Rust's borrow checker?


I have a fundamental question about my understanding of the Rust borrow checker, versus the JVM's garbage collector.

I have attempted to dive into understanding how the various garbage collection implementations (like G1 and CMS) work in Java. And I've attempted to browse this resource to understand how the borrow checker actually helps Rust decide when to free up memory.

Here is a section of the Rust resource linked above:

  • Next, we perform a number of dataflow analyses that compute what data is moved and when.

  • We then do a second type check across the MIR: the purpose of this type check is to determine all of the constraints between different regions.

  • Next, we do region inference, which computes the values of each region — basically, the points in the control-flow graph where each lifetime must be valid according to the constraints we collected.

  • At this point, we can compute the "borrows in scope" at each point.

What I fundamentally don't understand, in the whole "GC languages are terrible for performance because of stop the world" argument, is why can something like this not be implemented in the JVM?

Is it theoretically/provably not possible to perform the same steps listed above ("dataflow analyses that compute what data is moved and when") to decide when to proactively free memory, rather than the pause-based implementations we currently have in the JVM?


Solution

  • I would argue it's provably impossible -- akin to solving the Halting Problem -- but, in truth, it doesn't matter.

    The fact of the matter is that perfect is the enemy of good. The JVM already readily performs Escape Analysis which allows it to optimize away the allocation of objects which never need be objects. For example, a Point structure with two int fields may be optimized to just two int values on the stack.

    Escape Analysis is imperfect, and more often than not fails to prove the absence of escape, but the imperfection does not make it useless: it is still useful whenever it successfully proves the absence of escape!

    The data-flow analysis your refer to here would -- in the JVM -- be a variant, or supplementary analysis, for the existing Escape Analyses, and could help. Sometimes.

    Java, however, would get in the way. The Rust language was designed with a clear division between owned value and references/pointers from the beginning, and therefore the data-flow analysis is provided with the distinction. The Java language doesn't have this distinction, and thus any tracking of values is much more involved, and ultimately much less fruitful.

    Finally, do not forget that Rust also features Rc/Arc: reference-counted pointers, for when the static scopes are insufficient to express the logic of the application. Some such dynamic mechanism would also be required for Java/the JVM. GC is such a mechanism.