Valery Silaev's Blog

if it ain't broken we'll break it

This post is a first one in a series dedicated to continuations support in JDK 1.8 – namely, continuations usage with labmdas (anonymous functions of SAM interfaces) and Stream API (java.util.stream).

When Oracle (and formerly Sun) developing next Java version, the backward compatibility is the one of the primary concerns. API compatibility, bytecode compatibility, whatever. But for tool vendors, like ones who develop compilers, IDE, ad-hoc bytecode enhancers / generators, runtime utilities relying on reflection, almost every new Java release is always a wake-up call to keep their products up to date.

JDK 1.1 adds inner classes and anonymous classes – a pretty convenient way to declare a class in-place. However, this is a great example of the leaked abstraction – tool vendors had to cope with automatically generated class names, with automatically generated constructors of non-static inner classes, with hidden accessor methods generated to use private members (fields and methods) between inner/outer classes.

Next big move was JDK 1.5 – generics added a lot of fun to the daily routine of tools vendors. Generic signatures in class/method/field/parameter/variable declarations, type erasures, covariant method return types, automatically generated bridge methods… oh my! Overwhelming list of futures to support! What can be better than upgrading thousands lines of the code to support all of this! However, this Java release brings us annotations – the real impulse to revisit all our dynamic code generation techniques anyway. And the community responded promptly – AOP-specific libraries and tools, (revisited) dependency injection techniques, mappings for JPA and XML, fully refactored JEE and so on, and so on…

Then it was JDK 1.7 with INVOKEDYNAMIC. Not a ground-shaking change for the majority of existed Java tools, but a great value for the authors of JVM-compiled languages. Just recall: JRuby (Ruby on Java) beats Ruby (Ruby on C) in terms of performance! Isn’t this amazing?! But… At that time I had not expected how this dynamic invocation gun shot at Tascalate Javaflow library with JDK 1.8 release…

So, now we are running our applications on JDK 1.8. And the number of additional features that should be taken on account by tool vendors is overwhelming. Default methods in interfaces, static methods in interfaces – supporting this is not a complex task per se, but the fact, that method’s bodies are possible inside an interface, invalidates a lot of the logic inside many tools (Tascalate JavaFlow were affected, too). However, the most ground-shaking addition was lambdas, to be precise – the way lambdas are created by compiler and Java runtime.

The key point in the phrase above is “by the compiler AND Java runtime”. This means that a related bytecode is not only generated at compile-time BUT at run-time as well. The lesser-known to the general public LambdaMetafactory class is heavily involved in this magic. The API notes for the class describes a process pretty well: the compiler desugars lambda function’s body to the auto-generated method of the enclosing class, put an INVOKEDYNAMIC instruction whenever the SAM interface is used, and the LambdaMetafactory class links this dynamic invocation with an actual interface implementation generated at run-time. And in its’ own turn, this on-the-fly implementation delegates processing to the desugared lambda body (created at compile-time). A thrilling process!

So, we cannot rely on the compile-time-only bytecode modification any longer. But the lack of the labmdas support would be a serious omission. Fortunately, we, Java developers, have good old agents inside JVM that plays for us. A pun intended: this is namely JavaAgents technology that was introduced back in JDK 1.5 days. The technology provides a facility to intercept class loading mechanism and enhance a bytecode before it’s seen by the JVM. It works even with bytecode generated at run-time.

So, to support continuable anonymous lambdas and method references, you have to download first Tascalate JavaFlow Instrumentation Agent from the latest release on GitHub. Then you must please add the following arguments to Java command line:

java -javaagent:<path-to-jar>/javaflow.instrument-continuations.jar <rest-of arguments>

The agent JAR file includes all necessary dependencies and requires no additional CLASSPATH settings. It’s possible to use this agent in conjunction with either Maven or Ant build tools supplied. Moreover, it’s even a recommended option – it helps to minimize the associated overhead of the instrumentation during class-loading process at run-time. However, using just instrumentation agent has its’ own benefits when you are developing and debugging code within your IDE of choise. Just specify the same :javaagent option for your Run/Debug configuration (screen-shot below) – and you are ready to execute quick “debug-fix” loops relying on IDE-s incremental compilation + JavaAgent instrumentation – as opposed to time-consuming full project rebuild with Mavent/Ant.

JavaAgent IDE Debug Settings.png

Next time we will explore concrete examples with continuable Java 8 lambdas as well as additional Tascalate JavaFlow utility classes that simplifies related tasks.

One thought on “Continuations in Java 8, Part I

Leave a comment