Inside Java Newscast #1 in-depth interpretation

This article is Inside Java Newscast #1 Personal experience and interpretation. Video address: click here

⎯⎯⎯⎯⎯⎯ Chapters ⎯⎯⎯⎯⎯⎯

  • 0:00 - Intro
  • 0:57 - Java 16 – Intro
  • 1:16 - Java 16 – Records
  • 1:43 - Java 16 – Type Pattern Matching
  • 1:58 - Java 16 – Sealed Classes - Preview
  • 2:25 - Java 16 – Stream API
  • 2:51 - Java 16 – HTTP/2 API
  • 3:14 - Java 16 – Unix Domain Sockets
  • 3:32 - Java 16 – Project Panama (Incubating)
  • 4:07 - Java 16 – JDK Flight Recorder
  • 4:39 - Java 16 – jpackage
  • 5:02 - Java 16 – Performance
  • 5:23 - Java 16 – Security
  • 5:48 - Java 16 – Ports
  • 5:55 - Java 16 – Deprecations/Limitations
  • 6:49 - Java 16 – Outro
  • 7:08 - Java 17
  • 7:22 - Java 17 – Another Port
  • 7:34 - Java 17 – Applet for Removal
  • 7:55 - Java 17 – Sealed Classes
  • 8:12 - Outro

Java 16 – Records

Relevant JEP address:

I have carefully studied the implementation of Records: refer to another article I wrote Some thoughts on Java Record - the use of default methods and the underlying implementation of generating relevant bytecode based on precompiling

In short, it is (you can see by looking at the bytecode after compilation). After compilation, insert the bytecode of relevant fields and methods according to the Record source code, including:

  1. Automatically generated private final field
  2. Auto generated full attribute constructor
  3. Auto generated public getter method
  4. Automatically generated hashCode(), equals(), toString() method:

    1. As can be seen from the bytecode, the underlying implementation of these three methods is another invokeDynamic method
    2. Objectmethods. Is called bootstrap method in Java class

I also made a joke about this. I thought this was the implementation of Project Valhala's Inline Object (refer to my series: JEP tasting series ), I also went to StackOverflow to ask why this Record can have a wait() method and can be synchronized (because if it is an Inline Object of Project Valhala, there is no object header of a common class, and the synchronization can not be realized by the method of a common class object). As a result..... At last, boss Goetz saw at a glance that I had misunderstood:

What scene was the Record feature designed to adapt to and why some designs were abandoned? Please refer to this article by Gotez java-14-feature-spotlight The main highlights are summarized as follows:

1. The most common use of Java records is multiple returned results of methods. Originally, we may need to encapsulate them with objects such as Pair or Tuple in Apache commons, or create a new type ourselves. You can now use Record.

2. The second common application is to keep the original objects and reduce operations in the process of transmission in the Stream, such as Stream sorting:

List<Player> topN
        = players.stream()
             .sorted(Comparator.comparingInt(p -> getScore(p)))
             .limit(N)
             .collect(toList());

In this way, getScore(p) will be called once for each comparison, and the number of calls is O(n^2). With Record, the operation can be reduced with less code and changes:

record PlayerScore(Player player, Score score) {
    // convenience constructor for use by Stream::map
    PlayerScore(Player player) { this(player, getScore(player)); }
}

List<Player> topN
    = players.stream()
             .map(PlayerScore::new)
             .sorted(Comparator.comparingInt(PlayerScore::score))
             .limit(N)
             .map(PlayerScore::player)
             .collect(toList());

Finally, I would like to recommend some analysis and Thoughts on the serialization of records I wrote: Some thoughts on Java Record - serialization related

Java 16 – Type Pattern Matching

Relevant JEP address:

Type pattern matching has always been a popular feature. If it is combined with the Sealed Class feature and Patterns in switch in the next section, it will have better results. We will explain this in more detail in the next section

Nicolai's description of Type Pattern Matching

Nicolai's article The description of Type Pattern Matching is very detailed and summarized as follows:

The original code needs to be written as follows:

void feed(Animal animal) {
    if (animal instanceof Elephant) {
        ((Elephant) animal).eatPlants();
    }
    else if (animal instanceof Tiger) {
        ((Tiger) animal).eatMeat();
    }
    
}

It can now be written as follows:

void feed(Animal animal) {
    if (animal instanceof Elephant elephant)
        elephant.eatPlants();
    else if (animal instanceof Tiger tiger)
        tiger.eatMeat();
}

Null pointer judgment is not required because instanceof has its own null judgment, and the qualified Type Pattern Matching variable will not be null. In addition, Type Pattern Matching does not support upward matching because it is meaningless, that is, the following code will compile and report an error:

public void upcast(String string) {
    // compile error
    if (string instanceof CharSequence sequence)
        System.out.println("Duh");
}

Another common place is to implement equals:

// old
@Override
public boolean equals(Object o) {
    if (this == o)
        return true;
    if (!(o instanceof Equals))
        return false;
    Type other = (Type) o;
    return someField.equals(other.someField)
        && anotherField.equals(other.anotherField);
}

// new
@Override
public final boolean equals(Object o) {
    return o instanceof Type other
        && someField.equals(other.someField)
        && anotherField.equals(other.anotherField);
}

In fact, Type Pattern Matching is a syntax sugar

In fact, this feature is a syntax sugar. We can simply test it:

public class TypePatternMatching {
    public static void main(String[] args) {
        Object object = new Object();
        if (object instanceof String s) {
            System.out.println("a");
        }
    }
}

View compiled bytecode:

public class test.TypePatternMatching {
  public test.TypePatternMatching();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: astore_1
       8: aload_1
       9: instanceof    #7                  // class java/lang/String
      12: ifeq          28
      15: aload_1
      16: checkcast     #7                  // class java/lang/String
      19: astore_2
      20: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: ldc           #15                 // String a
      25: invokevirtual #17                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      28: return
}

It can be seen that the bytecode is actually written in the same way as the following:

public static void main(String[] args) {
    Object object = new Object();
    if (object instanceof String) {
        String s = (String)object;
        System.out.println("a");
    }

}

You can decompile this class and you can see it.

Java 16 – Sealed Classes - Preview

Sealed Class has been released in Java 17. The related JEP s are as follows:

In some cases, we may want to enumerate all implementation classes of an interface, for example:

interface Shape { }
record Circle(double radius) implements Shape { }
record Rectangle(double width, double height) implements Shape { }

double area(Shape shape) {
    if (shape instanceof Circle circle)
        return circle.radius() * circle.radius() * Math.PI;
    if (shape instanceof Rectangle rect)
        return rect.width() * rect.height();
    throw new IllegalArgumentException("Unknown shape");
}

How can we be sure that we have enumerated all shapes? The Sealed Class feature solves this problem for us. The Sealed Class can decide which classes can inherit this class when declaring:

sealed interface Shape permits Rectangle, Circle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
double area(Shape shape) {
    if (shape instanceof Circle circle)
        return circle.radius() * circle.radius() * Math.PI;
    if (shape instanceof Rectangle rect)
        return rect.width() * rect.height();
    throw new IllegalArgumentException("Unknown shape");
}

Sealed Class (which can be abstract class or interface) needs to specify the names of all implementation classes when declaring. For inherited classes, there are the following restrictions:

  • The inherited class of Sealed Class must be under the same module as Sealed Class. If no module is specified, it must be under the same package
  • Each inherited class must inherit Sealed Class directly, not indirectly
  • Each inherited class must be one of the following three:

    • The class of final, Java Record itself is final
    • sealed class, you can further specify which subclasses will be implemented
    • The non Sealed Class is also an extension, but it breaks the limitation of Sealed Class. Sealed Class does not know or care about the subclasses of this inherited class.

For example:

sealed interface Shape permits Rectangle, Circle, Triangle, WeirdShape {}
    
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}


sealed interface Triangle extends Shape permits RightTriangle, NormalTriangle {}
record RightTriangle(double width, double height) implements Triangle {}
record NormalTriangle(double width, double height) implements Triangle {}

static non-sealed class WeirdShape implements Shape {}
class Star extends WeirdShape {}

double area(Shape shape) {
    if (shape instanceof Circle circle)
        return circle.radius() * circle.radius() * Math.PI;
    if (shape instanceof Rectangle rect)
        return rect.width() * rect.height();
    if (shape instanceof RightTriangle rt)
        return rt.width() * rt.height() / 2;
    if (shape instanceof NormalTriangle nt)
        return nt.width() * nt.height() / 2;
    throw new IllegalArgumentException("Unknown shape");

}

If you combine the feature of Pattern Matching for switch, you can achieve more convenient writing. However, in Java 17, Pattern Matching for switch is still in Preview: JEP 406: Pattern Matching for switch (Preview) . We need to add -- enable preview to the compilation parameters and startup parameters, so that we can write the code as follows:

double area(Shape shape) {
    return switch (shape) {
        case Circle circle -> circle.radius() * circle.radius() * Math.PI;
        case Rectangle rect -> rect.width() * rect.height();
        case RightTriangle rt -> rt.width() * rt.height() / 2;
        case NormalTriangle nt -> nt.width( ) * nt.height() / 2;
        default -> throw new IllegalArgumentException("Unknown shape");
    };

}

Java 16 – Stream API update

There are two updates to the Stream API in Java 16. Here is an aside. If you want to see the differences between different JDK versions and which APIs have been added or deleted, you can view them through the following link:

The two versions in the path are the two versions to be compared. The interface is as follows:

At the same time, we can also find expired and obsolete API s and corresponding replacements through the built-in jdeps tool in JDK

jdeps --jdk-internals -R --class-path 'libs/*' $project

libs is the directory of all your dependencies, $project is the jar package of your project, and the sample output is as follows:

...
JDK Internal API                         Suggested Replacement
----------------                         ---------------------
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8
sun.reflect.Reflection                   Use java.lang.StackWalker @since 9

About this update, I wrote an article to analyze: Some thoughts on the new Stream interface in Java 16 , the core contents are summarized as follows:

Suppose there is a Record class of e-mail, including id, mailbox sent to and mailbox copied to:

record Mail(int id, Set<String> sendTo, Set<String> cc) {}

We want to find all the different contacts of a batch of emails, and finally put them into a List. It may be written as follows:

Set<String> collect = mails.stream().flatMap(mail -> {
    Set<String> result = new HashSet<>();
    result.addAll(mail.sendTo());
    result.addAll(mail.cc());
    return result.stream();
}).collect(Collectors.toSet());

However, it is obviously not elegant to write in this way. First, an additional Set and corresponding Stream are created for each mail, and the sendTo and cc of each mail are traversed twice (addAll once, and the subsequent Stream again). In fact, we only take cc and sendTo out of the mail to participate in the subsequent Stream. In this scenario, mapMulti is very suitable:

Set<String> collect = mails.stream().<String>mapMulti((mail, consumer) -> {
    mail.cc().forEach(consumer::accept);
    mail.sendTo().forEach(consumer::accept);
}).collect(Collectors.toSet());

It can be seen that:

  • The input parameter of mapMulti is a BiConsumer, which actually uses the consumer in its parameter to receive the subsequent objects participating in the Stream
  • The idea of mapMulti is to pass the object in the parameter that needs to participate in the subsequent Stream into the consumer to continue the Stream
  • consumer does not restrict the object type. If you want to restrict, you must add the formal parameter < string >, otherwise set < Object > is returned instead of set < string >

For Stream, toList is added to convert directly to List. Since the truncation operation in collect is not involved, it occupies less memory and requires less and faster operations than collect. Before converting to List, you need to collect(Collectors.toList()), and the generated List is ArrayList, which is variable. However, the newly added API, toList, generates an unmodifiable List, which is immutable. Therefore, the two APIs cannot be directly replaced with each other. You need to check and confirm that there is no change before replacing.

Java 16 – HTTP/2 API

Java 16 also introduces two JDK supplements about HTTP/2 API. Refer to:

Java 16 – Unix Domain Sockets

Relevant JEP:

Unix domain sockets are named in the form of local files, so that we can access local network connections like accessing local files. This is used for communication between different processes deployed on the same machine. The following is a simple BIO example:

//Create UnixDomainSocketAddress
Path socketFile = Path.of("/home/zhanghaxi/process1");
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketFile);

//Server monitoring
ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
serverChannel.bind(address);
SocketChannel channel = serverChannel.accept();

//Client connection

SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX);
channel.connect(address);

For an example of NIO, please refer to: https://docs.oracle.com/en/ja...

Compared with TCP/IP local loopback connection access, Unix Domain Sockets know that they are accessing local processes, so many checks and verifications (such as addressing and routing) are reduced. At the same time, because these checks are not needed, the packet size is smaller. The operating systems supporting Unix Domain Sockets include Linux, MacOS and Windows 10 or above and Windows Server 2019 or above

Java 16 – Project Panama (Incubating)

Project Panama is a project that makes Java more comprehensive. It is still in the state of incubation. At present, it mainly includes the following three API s:

  • Vector API: enables Java to use new CPU instructions, such as SIMD (Single Instruction Multiple Data) related instructions to optimize computing speed
  • Foreign Linker API: allows Java to directly call the system library without another layer of encapsulation through JNI.
  • Foreign memory access API: allows Java to directly operate external memory, breaks through the limitations of existing external memory APIs, and is also an API that can integrate and unify existing out of heap memory operations.

Vector API

Relevant JEP:

The most important application is SIMD (single instruction multiple data) processing using CPU. It provides multi-channel data flow through the program, which may have 4 or 8 channels or any number of single data elements. Moreover, the CPU organizes operations in parallel on all channels at once, which can greatly increase the CPU throughput. Through the Vector API, the Java team is trying to let Java programmers directly access it using java code; In the past, they had to program vector mathematics at the assembly code level, or use C/C + + with Intrinsic, and then provide it to Java through JNI.

A major optimization point is the loop. In the past, the loop (scalar loop) was executed on one element at a time, which was very slow. Now you can use the Vector API to convert scalar algorithms to faster data parallel algorithms. An example of using Vector:

//The test index is throughput
@BenchmarkMode(Mode.Throughput)
//Preheating is required to eliminate the impact of jit real-time compilation and JVM collection of various indicators. Since we cycle many times in a single cycle, preheating once is OK
@Warmup(iterations = 1)
//Single thread is enough
@Fork(1)
//Test times, we test 10 times
@Measurement(iterations = 10)
//The life cycle of a class instance is defined, and all test threads share an instance
@State(value = Scope.Benchmark)
public class VectorTest {
    private static final VectorSpecies<Float> SPECIES =
            FloatVector.SPECIES_256;

    final int size = 1000;
    final float[] a = new float[size];
    final float[] b = new float[size];
    final float[] c = new float[size];

    public VectorTest() {
        for (int i = 0; i < size; i++) {
            a[i] = ThreadLocalRandom.current().nextFloat(0.0001f, 100.0f);
            b[i] = ThreadLocalRandom.current().nextFloat(0.0001f, 100.0f);
        }
    }

    @Benchmark
    public void testScalar(Blackhole blackhole) throws Exception {
        for (int i = 0; i < a.length; i++) {
            c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
        }
    }

    @Benchmark
    public void testVector(Blackhole blackhole) {
        int i = 0;
        //A multiple of the length of data processed at one time by specialties that is higher than the length of the array
        int upperBound = SPECIES.loopBound(a.length);
        //Deal with specifications every cycle Length() so much data
        for (; i < upperBound; i += SPECIES.length()) {
            // FloatVector va, vb, vc;
            var va = FloatVector.fromArray(SPECIES, a, i);
            var vb = FloatVector.fromArray(SPECIES, b, i);
            var vc = va.mul(va)
                    .add(vb.mul(vb))
                    .neg();
            vc.intoArray(c, i);
        }
        for (; i < a.length; i++) {
            c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(VectorTest.class.getSimpleName()).build();
        new Runner(opt).run();
    }
}

Note that using the incubated Java feature requires additional startup parameters to expose the module. Here is -- add modules JDK incubator. Vector, these parameters need to be added during javac compilation and Java operation. Use IDEA, that is:

Test results:

Benchmark               Mode  Cnt         Score         Error  Units
VectorTest.testScalar  thrpt   10   7380697.998 ± 1018277.914  ops/s
VectorTest.testVector  thrpt   10  37151609.182 ± 1011336.900  ops/s

For other uses, please refer to: fizzbuzz-simd-style , this is an interesting article (although the performance optimization is not only due to SIMD, but also due to algorithm optimization, ha ha)

Foreign Linker API

Relevant JEP:

Through this API, we can use pure Java code to call the system library, for example, use Java code to pop up a Windows prompt box:

The above example comes from https://headcrashing.wordpres... , you can check it out if you are interested

Foreign-Memory Access API

Many popular high-performance Java frameworks and middleware use off heap memory, but the API for operating off heap memory in Java is not perfect at present:

If these API s are developed, using Java to manipulate memory will be easier to understand and more efficient

Java 16 – JDK Flight Recorder

JFR is my favorite Java feature function. I have written many articles on JFR and used JFR to locate many performance bottlenecks and online problems. Please refer to the following series or articles:

In Java 16, for JFR, JFR Stream exposed through JMX is added on the basis of JFR Stream introduced in Java 14. Originally, we can only consume JFR events internally. Now we can consume JFR events remotely through JMX: JDK-8253898: JFR: Remote Recording Stream

Java 16 – jpackage

Relevant JEP:

This is a tool for packaging Java programs into installable packages. Currently, the supported operating systems and formats include:

  • Linux: deb and rpm
  • macOS: pkg and dmg
  • Windows: msi and exe

You can refer to this article for a trial: Building Self-Contained, Installable Java Applications with JEP 343: Packaging Tool

Java 16 - Performance

There are many performance related updates

Hotspot implements Elastic Metaspace

Relevant JEP: Elastic Metaspace

In the original meta space implementation, each class loader occupies a separate meta space abstraction. When the class loader is recycled, this memory is released but will not be returned to the system, but will continue to be reused by other class loaders. The system memory occupation of the meta space will only increase and will not decrease, that is, the memory will not be returned to the system. This is now optimized to dynamically scale the meta space. This detailed source code analysis, I will issue a later issue similar to TLAB analysis of the hardest core in the whole network The article analyzes this.

G1 and Parallel GC optimization

If you want to learn more about this optimization, you can refer to this article: JDK 16 G1/Parallel GC changes

ZGC optimization

Relevant JEP:

ZGC has basically made each phase of GC concurrent, and STW is still required for GC root scanning. This JEP optimizes the thread stack scanning in GC root scanning, so that the scanning can also be "semi parallelized". I will also make a detailed analysis of this in the future.

Shenandoah GC optimization

Java 16 – Security

For security related optimization, please refer to: JDK 16 Security Enhancements

Java 16 – Deprecations/Limitations

Primitive Wrapper Warnings

Relevant JEP:

This is an exciting update to pave the way for my long-awaited Project Valhala (yes, I misunderstood the Record before).

At present, the constructors of encapsulated type classes (such as Integer) of the original type are marked as expired and will be removed in future versions and replaced with the static method valueOf() in them.

I wrote a separate article to analyze this. Refer to: JEP interpretation and tasting series 4 - paving the way for Project Valhalla in Java 16

Strong Encapsulation By Default

Relevant JEP:

In order to promote Java modularization, the feature of -- allegal access has been modified. Before Java 16, the default is permit. When accessing a package that is not open, you will be prompted for the first time, but it can still run normally:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by j9ms.internal.Nimbus
    (file:...) to constructor NimbusLookAndFeel()
WARNING: Please consider reporting this
    to the maintainers of j9ms.internal.Nimbus
WARNING: Use --illegal-access=warn to enable warnings
    of further illegal reflective access operations
WARNING: All illegal access operations will be denied
    in a future release

Java 16 is deny. That is, illegal package access is prohibited by default. Users can modify it through the startup parameter -- illegal access = permit. Java 17 removes this parameter. In addition, the startup parameter is invalid. There will be a prompt and an error will be reported when accessing the unexposed internal package, such as:

var dc = ClassLoader.class.getDeclaredMethod("defineClass",
        String.class,
        byte[].class,
        int.class,
        int.class);
dc.setAccessible(true);

Run with the startup parameter -- illegal access = warn:

OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @378bf509
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
    at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
    at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)

However, by starting the parameter -- add - opens Java base/java. Lang = all-unnamed can still break packet control

Keywords: Java

Added by carleyvibe on Fri, 07 Jan 2022 04:46:05 +0200