Tuesday, December 26, 2023

When concurrency bites (yet again!): class initialization deadlocks

Concurrent and parallel programming on JVM platform has never been easy: yes, it is significantly safer and simpler than in most programming languages (thanks to outstanding concurrency support by Java language, standard library and JVM) but still, surprises pop up from time to time.

In today's post we are going to learn how a seemingly innocent implementation may intermittently deadlock during the class initialization under some circumstances. To begin, here is the Options class we are going to work with along the way.

package com.example;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public final class Options {
    public final static Options EMPTY = new Builder().build();
    private final Map<String, String> options;
    
    public Options(final Map<String, String> options) {
        this.options = new HashMap<>(Objects.requireNonNull(options));
    }

    @Override
    public String toString() {
        return "Options=" + options.toString();
    }

    public static class Builder {
        public static final Options EMPTY = new Builder().build();
        private final Map<String, String> options = new HashMap<>();

        public Builder option(final String name, final String value) {
            this.options.put(name, value);
            return this;
        }

        public Options build() {
            return new Options(options);
        }
    }
}

The snippet above implements a variation of the Builder pattern (in this case, for Options class). Although the sample is somewhat made up, the similarities to the existing implementations aren't (for example, please check Parts of Rest High-Level Client not thread-safe out). At a glance, it seems to be no-brainer, the code compiles and runs perfectly fine (the console output serves as a proof in this case).

    public static void main(String[] args) {
        System.out.println("New instance: " + new Options.Builder().build());
        System.out.println("EMPTY Options instance: " + Options.EMPTY);
        System.out.println("EMPTY Options.Builder instance: " + Options.Builder.EMPTY);
    }

Once executed, we should see a few lines printed out.

New instance: Options={}
EMPTY Options instance: Options={}
EMPTY Options.Builder instance: Options={}

The attentive reviewer may spot something fishy about this implementation, specifically related to EMPTY static fields: there is an explicit bidirectional (or better to say, circular) dependency between Options and Options.Builder classes. But JVM is able to handle that, non issue, right? Well, yes and no, and to understand why, let us take a look at the variation of the initialization sequence that is triggered concurrently:

    public static void main(String[] args) {
        try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
            executor.submit(() -> System.out.println("New instance: " + new Options.Builder().build()));
            executor.submit(() -> System.out.println("EMPTY Options instance: " + Options.EMPTY));
        }
    }

Surprisingly (or not?), the execution of this code intermittently hangs the JVM. If we look into the thread dump, the reason becomes very clear (thanks to JDK-8288064: Class initialization locking, the output has been redacted a bit to highlight the clues).

"pool-1-thread-1" #29 [8432] prio=5 os_prio=0 cpu=0.00ms elapsed=499.54s allocated=7800B defined_classes=1 tid=0x000002609f22b680 nid=8432 waiting on condition  [0x000000be184fe000]
   java.lang.Thread.State: RUNNABLE
	at com.example.Options$Builder.build(Options.java:35)
	- waiting on the Class initialization monitor for com.example.Options
	at com.example.Options$Builder.<clinit>(Options.java:26)
    ...

"pool-1-thread-2" #30 [19688] prio=5 os_prio=0 cpu=0.00ms elapsed=499.54s allocated=5184B defined_classes=1 tid=0x000002609f233e40 nid=19688 waiting on condition  [0x000000be185fe000]
   java.lang.Thread.State: RUNNABLE
	at com.example.Options.<clinit>(Options.java:9)
	- waiting on the Class initialization monitor for com.example.Options$Builder
    ...

The Options and Options.Builder classes deadlock during initialization (and indeed, Options needs Options.Builder to initialize EMPTY static field however Options.Builder needs Options to initialize own EMPTY static field). The JLS (Java Language Specification) is very clear on that (but how many of us have read the specification anyway?):

Because the Java programming language is multithreaded, initialization of a class or interface requires careful synchronization, since some other thread may be trying to initialize the same class or interface at the same time. There is also the possibility that initialization of a class or interface may be requested recursively as part of the initialization of that class or interface; for example, a variable initializer in class A might invoke a method of an unrelated class B, which might in turn invoke a method of class A. The implementation of the Java Virtual Machine is responsible for taking care of synchronization and recursive initialization by using the following procedure. - 12.4.2. Detailed Initialization Procedure

This is by no means a new issue that could be solved by restructuring the code, it has been known for years (JDK-4891511: Deadlock in class initialization specification, JLS 2nd ed. 12.4.2) but it still bites and not easy to troubleshoot (JDK-8059913: Deadlock finder is unable to find deadlocks caused by <clinit>). Luckily, JVM diagnostics is getting better and upcoming JDK-22 release will bring yet another improvement as part of JDK-8316229: Enhance class initialization logging, it should definitely help to debug apparent class initialization deadlocks (when JVM has class+init debug logging enabled using -Xlog:class+init=debug command line option).

...
[0.089s][debug][class,init] Thread "pool-1-thread-1" is initializing com.example.Options$Builder
[0.089s][debug][class,init] Thread "pool-1-thread-2" is initializing com.example.Options
[0.089s][info ][class,init] 511 Initializing 'com/example/Options$Builder' (0x000002b9dd001000) by thread "pool-1-thread-1"
[0.089s][info ][class,init] 512 Initializing 'com/example/Options' (0x000002b9dd001218) by thread "pool-1-thread-2"
[0.089s][debug][class,init] Thread "pool-1-thread-1" recursively initializing com.example.Options$Builder
[0.089s][debug][class,init] Thread "pool-1-thread-2" waiting for initialization of com.example.Options$Builder by thread "pool-1-thread-1"
[0.089s][debug][class,init] Thread "pool-1-thread-1" waiting for initialization of com.example.Options by thread "pool-1-thread-2"
...

Undoubtedly, JVM is very sophisticated piece of technology and each release (which is happening every six months these days) brings more features, bugfixes and improvements. Despite getting smarter, JVM still requires developers to be aware of its limitations and think about the code they write (would co-pilots and LLMs help with that is yet to be seen).

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Thursday, October 5, 2023

JDK-21: green threads are officially back!

The JDK-21 is there, bringing virtual threads (back) into JVM as a generally available feature (if you are old enough like myself, you might have remembered that in Java 2 releases prior to 1.3 the JVM used its own threads library, known as green threads, to implement threads in the Java platform). This is big, but what else is coming?

  • JEP-431: Sequenced Collections: introduces new interfaces to represent collections with a defined encounter order. Each such collection has a well-defined first element, second element, and so forth, up to the last element. It also provides uniform APIs for accessing its first and last elements, and for processing its elements in reverse order.

    The following new interfaces have been introduced (and retrofitted into the existing collections type hierarchy), potentially a breaking change for some library implementors:

  • JEP-439: Generational ZGC: improves application performance by extending the Z Garbage Collector (ZGC) to maintain separate generations for young and old objects. This will allow ZGC to collect young objects — which tend to die young — more frequently.

    By default, the -XX:+UseZGC command-line option selects non-generational ZGC, but to select the Generational ZGC, additional command line option -XX:+ZGenerational is required:

    $ java -XX:+UseZGC -XX:+ZGenerational ...
    

  • JEP-440: Record Patterns: enhances the Java programming language with record patterns to deconstruct record values. Record patterns and type patterns can be nested to enable a powerful, declarative, and composable form of data navigation and processing. This is certainly a huge step towards having a powerful, feature-rich pattern matching capabilities in the language:

        interface Host {}
        record TcpHost(String name, int port) implements Host {}
        record HttpHost(String scheme, String name, int port) implements Host {}
        

    The are several places the records could be deconstructed, instanceof check being one of those:

        final Host host = new HttpHost("https", "localhost", 8080);
        if (host instanceof HttpHost(var scheme, var name, var port)) {
            ... 
        } else if (host instanceof TcpHost(var name, var port)) {
            ...
        }
        
  • JEP-441: Pattern Matching for switch: enhances the Java programming language with pattern matching for switch expressions and statements. Extending pattern matching to switch allows an expression to be tested against a number of patterns, each with a specific action, so that complex data-oriented queries can be expressed concisely and safely.

    Considering the example with the records deconstruction from above, we could use record patterns in switch expressions too:

            var hostname = switch(host) {
                case HttpHost(var scheme, var name, var port) -> name;
                case TcpHost(var name, var port) -> name;
                default -> throw new IllegalArgumentException("Unknown host");
            };
        

    But the switch patterns are much more powerful, with guards to pattern case labels, null labels, etc.

            final Object obj = ... ; 
            var o = switch (obj) {
                case null ->  ... ;
                case String s -> ... ;
                case String[] a when a.length == 0 -> ... ;
                case String[] a -> ... ;
                default ->  ... ;
            }
        
  • JEP-444: Virtual Threads: introduces virtual threads to the Java Platform. Virtual threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications. The virtual threads and executors could be used along the traditional ones, following the same familiar API:

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            executor.submit(() -> {
                    ...
            });
        }  
        

    Some of the quirks of the virtual threads we have discussed previously here and here, but there is one more: you could use them in parallel streams, but should you? The answer is a bit complicated, so referring you to Virtual Threads and Parallel Streams article if you are looking for clarity.

    The JDK tooling (like jcmd and jfr) has been updated to include the information about virtual threads where applicable.

    The jcmd thread dump lists virtual threads that are blocked in network I/O operations and virtual threads that are created by the ExecutorService interface. It does not include object addresses, locks, JNI statistics, heap statistics, and other information that appears in traditional thread dumps (as per Viewing Virtual Threads in jcmd Thread Dumps).
    Java Flight Recorder (JFR) can emit these events related to virtual threads (as per Java Flight Recorder Events for Virtual Threads):
    • jdk.VirtualThreadStart and jdk.VirtualThreadEnd (disabled by default)
    • jdk.VirtualThreadPinned (enabled by default with a threshold of 20 ms)
    • jdk.VirtualThreadSubmitFailed (enabled by default)

    It is worth noting that Oracle has published a comprehesive guide on virtual threads as par of JDK-21 documentation update.

  • JEP-449: Deprecate the Windows 32-bit x86 Port for Removal: deprecates the Windows 32-bit x86 port, with the intent to remove it in a future release.

  • JEP-451: Prepare to Disallow the Dynamic Loading of Agents: issues warnings when agents are loaded dynamically into a running JVM. These warnings aim to prepare users for a future release which disallows the dynamic loading of agents by default in order to improve integrity by default. Serviceability tools that load agents at startup will not cause warnings to be issued in any release.

    Running with -XX:+EnableDynamicAgentLoading on the command line serves as an explicit "opt-in" that allows agent code to be loaded into a running VM and thus suppresses the warning. Running with -XX:-EnableDynamicAgentLoading disallows agent code from being loaded into a running VM and can be used to test possible future behavior.

    In addition, the system property jdk.instrument.traceUsage can be used to trace uses of the java.lang.instrument API. Running with -Djdk.instrument.traceUsage or -Djdk.instrument.traceUsage=true causes usages of the API to print a trace message and stack trace. This can be used to identify agents that are dynamically loaded instead of being started on the command line with -javaagent.

  • JEP-452: Key Encapsulation Mechanism API: introduces an API for key encapsulation mechanisms (KEMs), an encryption technique for securing symmetric keys using public key cryptography. The new APIs are centered around javax.crypto.KEM and javax.crypto.KEMSpi abstractions.

  • JEP-430: String Templates (Preview): enhances the Java programming language with string templates. String templates complement Java's existing string literals and text blocks by coupling literal text with embedded expressions and template processors to produce specialized results. This is a preview language feature and API.

  • JEP-453: Structured Concurrency (Preview): simplifies concurrent programming by introducing an API for structured concurrency. Structured concurrency treats groups of related tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability. This is a preview language feature and API.

  • JEP-443: Unnamed Patterns and Variables (Preview): enhances the Java language with unnamed patterns, which match a record component without stating the component's name or type, and unnamed variables, which can be initialized but not used. Both are denoted by an underscore character, _. This is a preview language feature.

  • JEP-445: Unnamed Classes and Instance Main Methods (Preview): evolves the Java language so that students can write their first programs without needing to understand language features designed for large programs. Far from using a separate dialect of Java, students can write streamlined declarations for single-class programs and then seamlessly expand their programs to use more advanced features as their skills grow. This is a preview language feature.

  • JEP-446: Scoped Values (Preview): introduces scoped values, values that may be safely and efficiently shared to methods without using method parameters. They are preferred to thread-local variables, especially when using large numbers of virtual threads. This is a preview language API.

    In effect, a scoped value is an implicit method parameter. It is "as if" every method in a sequence of calls has an additional, invisible, parameter. None of the methods declare this parameter and only the methods that have access to the scoped value object can access its value (the data). Scoped values make it possible to pass data securely from a caller to a faraway callee through a sequence of intermediate methods that do not declare a parameter for the data and have no access to the data.

  • JEP-442: Foreign Function & Memory API (3rd Preview): introduces an API by which Java programs can interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI. This is a preview language API.

  • JEP-448: Vector API (6th Incubator): introduces an API to express vector computations that reliably compile at runtime to optimal vector instructions on supported CPU architectures, thus achieving performance superior to equivalent scalar computations.

Those JEPs are the themes of JDK-21 but what other features are coming? There are quite a few to unpack to be fair.

The JDK-21 changeset looks already impressive but ... we are not done yet, let us walk through the standard library changes.

From the security perspective, JDK-21 is pretty packed with enhancements. Some of them we have highlighted above but a few more deserve special mentions (if you need a comprehensive look, please check out JDK 21 Security Enhancements article):

From all perspectives, JDK-21 looks like the release worth migrating to (it is supposed to be LTS), despite the fact there are unforeseen delays announced by some vendors.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Tuesday, June 27, 2023

Java's SecurityManager in the age of virtual threads

The JDK-21 is just around the corner, bringing the virtual threads (JEP 444: Virtual Threads) to the mainstream, at least this is the plan so far. For vast majority of the projects out there the virtual threads open up a huge number of opportunities but for some - add new headaches.

More specifically, let us talk about niche projects that still rely on SecurityManager. The SecurityManager has been deprecated for removal in JDK-17 (JEP 411: Deprecate the Security Manager for Removal) and disallowed in JDK-18 (JDK-8270380). Its usage is discouraged but it is still there and is used in production systems.

Since JDK-21 early builds are available to everyone (as of this moment, the latest build is 21-ea+28-2377), let us find out what is happening when SecurityManager meets virtual threads. The code snippet below would serve as an example of the application that installs the SecurityManager and tries to fetch, well, the content of JEP 444, for sake of doing network calls. In the first attempt, we are going to use standard thread pool (operating system threads).

public class Starter {
    public static void main(String[] args) throws Exception {
        System.setSecurityManager(new SecurityManager());
        try (var executor = Executors.newSingleThreadExecutor()) {
            var future = executor.submit(() -> fetch("https://openjdk.org/jeps/444"));
            System.out.println(future.get());
        }
    }

    private static String fetch(String url) throws MalformedURLException, IOException, URISyntaxException {
        try (var in = new URI(url).toURL().openStream()) {
            return new String(in.readAllBytes(), StandardCharsets.UTF_8);
        }
    }
}

If we run this example using just java (thanks to JEP 330: Launch Single-File Source-Code Programs), it is going to fail with the java.security.AccessControlException:

$ java -Djava.security.manager=allow src/main/java/com/example/Starter.java

...
Exception in thread "main" java.util.concurrent.ExecutionException: java.security.AccessControlException: access denied ("java.net.SocketPermission" "openjdk.org:443" "connect,resolve")
        at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
        at com.example.Starter.main(Starter.java:15)
        Suppressed: java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "modifyThread")
                at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:488)
                at java.base/java.security.AccessController.checkPermission(AccessController.java:1071)
                at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:411)
                at java.base/java.util.concurrent.ThreadPoolExecutor.checkShutdownAccess(ThreadPoolExecutor.java:764)
                at java.base/java.util.concurrent.ThreadPoolExecutor.shutdown(ThreadPoolExecutor.java:1394)
                at java.base/java.util.concurrent.Executors$DelegatedExecutorService.shutdown(Executors.java:759)
                at java.base/java.util.concurrent.Executors$AutoShutdownDelegatedExecutorService.shutdown(Executors.java:846)
                at java.base/java.util.concurrent.ExecutorService.close(ExecutorService.java:413)
                at com.example.Starter.main(Starter.java:13)
                at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
                at java.base/java.lang.reflect.Method.invoke(Method.java:580)
                at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:484)
                at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:208)
                at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:135)
Caused by: java.security.AccessControlException: access denied ("java.net.SocketPermission" "openjdk.org:443" "connect,resolve")
        at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:488)
        at java.base/java.security.AccessController.checkPermission(AccessController.java:1071)
        at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:411)
        at java.base/java.lang.SecurityManager.checkConnect(SecurityManager.java:905)
        at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:619)
        at java.base/sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:264)
        at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:377)
        at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1237)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1123)
        at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1675)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1599)
        at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:223)
        at java.base/java.net.URL.openStream(URL.java:1325)
        at com.example.Starter.fetch(Starter.java:24)
        at com.example.Starter.lambda$main$0(Starter.java:14)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)

That is kind of expected, we need to craft the policy to allow the connection over socket to openjdk.org host, the minimal one we need is below, stored in src/main/resources/security.policy:

grant { 
    permission java.lang.RuntimePermission "modifyThread";
    permission java.net.SocketPermission "openjdk.org:443", "connect,resolve";
};

Now, if we rerun this example with this policy, it should print out the content of the JEP into the console (in HTML format):

$ java -Djava.security.manager=allow  -Djava.security.policy=src/main/resources/security.policy src/main/java/com/example/Starter.java

...
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
...

Awesome, so let just switch over to virtual threads! It should just work, right?

public class Starter {
    public static void main(String[] args) throws Exception {
        System.setSecurityManager(new SecurityManager());
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            var future = executor.submit(() -> fetch("https://openjdk.org/jeps/444"));
            System.out.println(future.get());
        }
    }

    private static String fetch(String url) throws MalformedURLException, IOException, URISyntaxException {
        try (var in = new URI(url).toURL().openStream()) {
            return new String(in.readAllBytes(), StandardCharsets.UTF_8);
        }
    }
}

Or should it?

$ java -Djava.security.manager=allow  -Djava.security.policy=src/main/resources/security.policy src/main/java/com/example/Starter.java

...
Exception in thread "main" java.util.concurrent.ExecutionException: java.security.AccessControlException: access denied ("java.net.SocketPermission" "openjdk.org:443" "connect,resolve")
        at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
        at com.example.Starter.main(Starter.java:15)
Caused by: java.security.AccessControlException: access denied ("java.net.SocketPermission" "openjdk.org:443" "connect,resolve")
        at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:488)
        at java.base/java.security.AccessController.checkPermission(AccessController.java:1071)
        at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:411)
        at java.base/java.lang.SecurityManager.checkConnect(SecurityManager.java:905)
        at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:619)
        at java.base/sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:264)
        at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:377)
        at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1237)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1123)
        at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1675)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1599)
        at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:223)
        at java.base/java.net.URL.openStream(URL.java:1325)
        at com.example.Starter.fetch(Starter.java:20)
        at com.example.Starter.lambda$main$0(Starter.java:14)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.lang.VirtualThread.run(VirtualThread.java:311)

If you are surprised, so was I. But we really shouldn't be if we read JEP 444: Virtual Threads carefully enough. It says clearly:

  • Virtual threads have no permissions when running with a SecurityManager set.

Why is that? Back in the days, the rumors were being spread that Project Loom was one of the reasons to kick SecurityManager out, the two didn't play well together. True or not, here we are.

For better or worse, the SecurityManager has transitioned from being the source of annoying deprecation warnings to rather a serious obstacle on the route of adopting recent JDK features. The time of making hard decisions is approaching very fast.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Wednesday, March 22, 2023

JDK-20: the most boring JDK release yet?

Still hot off the press, JDK-20 is out! These are terrific news, but what about exciting new features? Although a number of JEPs made it into the release, all of them are either preview or incubation features:

  • JEP-429: Scoped Values (Incubator): introduces scoped values, which enable the sharing of immutable data within and across threads. They are preferred to thread-local variables, especially when using large numbers of virtual threads.

  • JEP-432: Record Patterns (Second Preview): enhances the Java programming language with record patterns to deconstruct record values. Record patterns and type patterns can be nested to enable a powerful, declarative, and composable form of data navigation and processing.

  • JEP-433: Pattern Matching for switch (Fourth Preview): enhances the Java programming language with pattern matching for switch expressions and statements. Extending pattern matching to switch allows an expression to be tested against a number of patterns, each with a specific action, so that complex data-oriented queries can be expressed concisely and safely.

    If you keen to learn more, please check out Using Pattern Matching publication, a pretty comprehensive overview of this feature.

  • JEP-434: Foreign Function & Memory API (Second Preview): introduces an API by which Java programs can interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI.

  • JEP-436: Virtual Threads (Second Preview): introduces virtual threads to the Java Platform. Virtual threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications.

  • JEP-437: Structured Concurrency (Second Incubator): simplifies multithreaded programming by introducing an API for structured concurrency. Structured concurrency treats multiple tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability.

  • JEP-438: Vector API (Fifth Incubator): introduces an API to express vector computations that reliably compile at runtime to optimal vector instructions on supported CPU architectures, thus achieving performance superior to equivalent scalar computations.

The pessimists may say this is the most boring release of JDK yet, but the optimist would argue that this is the calm before the storm (yes, I am talking about the next LTS release later this year, JDK-21). Nonetheless, there are quite a few notable changes to look at.

The standard library was the one benefited the most in JDK-20 release, let us take a closer look at what has changed:

The garbage collectors have got a considerable chunk of improvements (especially G1), covered by JDK 20 G1/Parallel/Serial GC changes in great details. To highlight just a few:

From the security perspective, it worth mentioning these changes:

  • JDK-8256660: Disable DTLS 1.0 by default: disables DTLS 1.0 by default by adding "DTLSv1.0" to the jdk.tls.disabledAlgorithms security property in the java.security configuration file.

  • JDK-8290368: Introduce LDAP and RMI protocol-specific object factory filters to JNDI implementation: introduces LDAP-specific factories filter (defined by jdk.jndi.ldap.object.factoriesFilter property) and RMI-specific factories filter (defined by jdk.jndi.rmi.object.factoriesFilter property). The new factory filters are consulted in tandem with the jdk.jndi.object.factoriesFilter global factories filter to determine if a specific object factory is permitted to instantiate objects for the given protocol.

This releases proves one more time that boring is not always bad. If you want to learn more about these (and other) features in JDK-20, I would highly recommend going over Java 20 πŸ₯± guide.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Tuesday, January 3, 2023

Project Loom in JDK-19: the benefits but with quirks

A few months have passed already since JDK-19 release, which we talked about previously in details. More and more developers are switching to JDK-19, turning their heads towards Project Loom and starting to play with virtual threads and structured concurrency (despite the incubation / preview status of these features). And it certainly makes sense, sooner or later, the JVM and API changes will be finalized, marking the era of the Project Loom production readiness.

In today's post, we are going to cover some not so obvious quirks (by-products of the Project Loom implementation) you should be aware of (or may run into) while switching to JDK-19 in the green-field or, more importantly, brown-field projects. Those may manifest even if you are not planning to use Project Loom just yet.

Let us kick it off with API changes. The code snippet below uses old fashioned java.lang.Thread class to spawn some work aside. The computation uses the instance of the Builder inner class, the implementation of the Builder::build() method is left off since it is not really important.

public class Starter {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            public void run() {
                final Builder builder = new Builder();
                builder.build();
            }
        };
        thread.start();
        thread.join();
    }

    private static class Builder {
        public void build() {
            // implementation details
        }
    }
}

The code compiles and runs just fine on any modern JDK, predating JDK-19. On JDK-19 however, it fails to compile, with somewhat cryptic error.

Unresolved compilation problems: 
	Cannot instantiate the type Thread.Builder
	The method build() is undefined for the type Thread.Builder

The rare example of how existing code may clash with API changes: as part of the Project Loom, the java.lang.Thread got a new public sealed interface Builder, which is being rightly picked by the compiler (instead of our Builder class) inside Thread's the subclass. The fix is easy (but may not look pretty), just use the qualified class name:

public class Starter {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            public void run() {
                final Starter.Builder builder = new Starter.Builder();
                builder.build();
            }
        };
        thread.start();
        thread.join();
    }

    private static class Builder {
        public void build() {
            // implementation details
        }
    }
}

Please notice that nonetheless JDK's preview features were not enabled, the preview APIs are still visible and taken into the consideration by the compiler. The issue has been reported (JDK-8287968) and the possible incompatibilities have been documented (JDK-8288416).

The next quirk we are going to look at is also related to java.lang.Thread but this time we would be using thread pools (executors) from the standard library. Let us assume we need an executor instance which tracks the moment when the new thread is started. One of the options to accomplish that is to use custom java.util.concurrent.ThreadFactory and override Thread::start() method.

public class Starter {
    public static void main(String[] args) throws Exception {
        final ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r) {
                    @Override
                    public void start() {
                        System.out.println("Thread has started!");
                        super.start();
                    }
                };
            }
        });
        
        executor.submit(() -> {}).get(1, TimeUnit.SECONDS);
        executor.shutdown();
    }
}

On JDKs prior to JDK-19, the expected message will be printed out in the console.

Thread has started!

But not in JDK-19: in scope of the Project Loom implementation, the thread pools and executors (notably ForkJoinPool and ThreadPoolExecutor) do not call Thread::start() method anymore. It does not matter if the preview features are enabled or not, and sadly, there is no workaround to simulate the desired behavior (the alternative Thread::start(ThreadContainer) replacement is not accessible). The issue has been reported and is still open as of today (JDK-8292027).

Great, so far we have seen some quirks caused by Project Loom irrespective of the fact it is used or not. Moving on, let us quickly summarize the constraints you may run into when using Project Loom (by enabling JDKs preview features) and virtual threads.

  • be aware of the limitations using synchronized blocks or methods in scope of virtual threads
  • be aware of the limitations using native methods or foreign functions in scope of virtual threads
  • be aware of the limitations some APIs (like file system) in the JDK have when called in scope to virtual threads

Two JEPs, the JEP-425: Virtual Threads (Preview) and JEP-436: Virtual Threads (Second Preview) offer quite a comprehensive overview with respect to the virtual thread implementation and limitations in JDK-19 and upcoming JDK-20, worth of your time reading them. Another good source I would recommend is Coming to Java 19: Virtual threads and platform threads published by Java Magazine last May.

It is fair to say that Project Loom is evolving really fast, and this is the kind of the feature JVM really screamed for. Yes, it has some limitations now, but there are high chances that in the future most of them will be lifted or worked through.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.