12 comments

[ 4.2 ms ] story [ 38.4 ms ] thread
Nice post!

A minor point:

> monitors are incompatible with coroutines

If by coroutines the author meant virtual threads, then monitors have always been compatible with virtual threads (which have always needed to adhere to the Thread specification). Monitors could, for a short while, degrade the scalability of virtual threads (and in some situations even lead to deadlocks), but that has since been resolved in JDK 24 (https://openjdk.org/jeps/491).

I meant polyfilled coroutines used by other JVM languages, like Kotlin. When you compile a coroutine to a state machine, yielding has to return from the machine; but JVM does not support unbalanced monitors, although it obviously does support unbalanced locking operations with normal mutexes.
(comment deleted)
On the subject

  void foo() {
    for (;;) {
      try { return; } 
      finally { continue; }
    }
  }
is my favorite cursed Java exceptions construct.
Just tested that in C# and it seems they made the smart decision to not allow shenanigans like that in a finally block:

CS0157 Control cannot leave the body of a finally clause

In JDK 25, you can run this code:

    $ cat App.java

    void main() {
      for (;;) {
        try { return; } 
        finally { continue; }
      }
    }

    $ java App.java
this broke my head. I think I haven't touched Java in a while and kept thinking continue should be in a case/switch so ittook a minute to back out of that alleyway before I even got what was wrong with this.
try/finally is effectively try/catch(Throwable) with copy all the code of the finally block prior to exiting the method. (Java doesn't have a direct bytecode support for 'finally')

Nothing that cursed.

It compiles to this:

  void foo() {
    for (;;) {
      try {
        continue;
        return; } 
      catch (Throwable t) { continue; }
    }
  }
Isn't this just an endless loop with extra steps?
Doesn't JRE has some limited form of decompilation in its JIT, as a pre-pass? IIRC, it reconstructs the basic blocks and CFG from the bytecode and does some minor optimizations before going on to regalloc and codegen.
Older versions of Java did try to have only one copy of the finally block code. To implement this, there were "jsr" and "ret" instructions, which allowed a method (a subroutine) to contain subroutines inside it. This even curseder implementation of finally is prohibited starting from version 51 class files (Java 7).