Showing posts with label jdk 22. Show all posts
Showing posts with label jdk 22. Show all posts

Friday, October 11, 2024

JDK-23: the messenger?

It's been a little less than few weeks since JDK-23 was released and we have not covered it yet! To be fair, this is not a big deal, each new release gets more and more attention, but nonetheless! And while this one in particular may not be looking too exciting (well, mostly all the features are in preview), it is a messenger of what is coming next (and that is really thrilling).

Anyway, there are quite a few things that JDK-23 delivers.

Along with the new features, there are some disruptive changes as well, notably:

On the bright side, there are a number of enhancements, bug fixes, tooling and GC improvements that are worth mentioning:

The changes on the standard library API side are quite moderate:

On the final note, it is good time to go over some security related updates that were introduced into JDK-23 (for more details, please check JDK 23 Security Enhancements):

If anything, JDK-23 is a solid release, ready for prime time. From the changes perfective, it looks low risk as well, so if you skipped JDK-22 for some reasons, JDK-23 may be the one.

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

Saturday, March 23, 2024

JDK-22: The JNI's grave?

Another six months passed and it is about time for a new JDK release: without further ado, please meet JDK-22. The theme of this release is obviously Foreign Function & Memory API that becomes generally available after numerous preview cycles. So what else is there?

  • JEP-454: Foreign Function & Memory API: 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.

    By all means, Foreign Function & Memory API (or FFM shortly), was eagerly awaited. For a long time JNI was the only mechanism to access native code on JVM and it has gained pretty infamous reputation of being brittle and outdated. FFM has emerged as a (substantially) better alternative and, after many rounds of previews, the API is finalized in JDK-22.

  • JEP-423: Region Pinning for G1: reduces latency by implementing region pinning in G1, so that garbage collection need not be disabled during Java Native Interface (JNI) critical regions.

  • JEP-456: Unnamed Variables & Patterns: enhances the Java programming language with unnamed variables and unnamed patterns, which can be used when variable declarations or nested patterns are required but never used. Both are denoted by the underscore character, _.

    To be fair, the positive impact of this small language change on the readability of Java programs is paramount. Familiar to Scala developers for years, it is here for Java developers now.

            if (content instanceof String s && s.startsWith("{")) {
                parseJson(s);
            } else if (content instanceof String s && s.startsWith("<")) {
                parseXml(s);
            } else if (content instanceof String _) {
                throw new IllegalArgumentException("The content type is not detected");
            };
      

    It becomes even more evident with records:

    
         sealed interface Response permits NoContentResponse, ContentResponse {}
        record NoContentResponse(int status) implements Response {}
        record ContentResponse(int status, byte[] content) implements Response {}
    
        public int status(final Response response) {
            return switch(response) {
                case NoContentResponse(var status) -> status;
                case ContentResponse(var status, _) -> status;
            };
        }
          

    And lamba functions:

        BiFunction<String, Number, String> f = (_, n) -> n.toString();
        
  • JEP-458: Launch Multi-File Source-Code Programs: enhances the java application launcher to be able to run a program supplied as multiple files of Java source code. This will make the transition from small programs to larger ones more gradual, enabling developers to choose whether and when to go to the trouble of configuring a build tool.

    This is a logical continuation of the JEP 330: Launch Single-File Source-Code Programs, delivered in JDK-11. With this change, Java could seriously challenge the status quo of the established scripting languages.

Couple of new and refined preview language features and APIs are also made into release, just briefly mentioning them here (and we would get back to them once finalized).

Every new JDK release comes with a long list of bugfixes, and in case of JDK-22, there are quite a few worth mentioning:

Moving off from bugs and regressions, let us take a look at the interesting new features that JDK-22 delivers across the board:

Last but not least, let us talk about the API changes (standard library) that went into this release.

From the security perspective, there are couple of notable changes to highlight (but please, do not hesitate to check out JDK 22 Security Enhancements for more details):

To finish up, it will be useful to mention a few regressions that ended up in JDK-22 release, the fixes are already scheduled for the upcoming major or patch releases:

Some may say that JDK-22 is a boring release, but I personally disagree: FFM APIs and formalizing _ usage are all but not boring features.

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

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.