Java17新功能概述插图

当我们想到新Java时™, 我们喜欢想象它像一辆来自受人尊敬的系列的时尚现代汽车——忠实于其根源,但速度快、舒适,并配备了驾驶所需的所有小发明。当然,更新的车型不断推出,但当你需要可靠和实用的时候,你就不会错了。

因此,请与我们一起探索使用Java17打开的所有新道路,我们将看到这条道路的走向。

Java意味着商业

当您不想在稳定性、安全性和支持之间做出选择时,Java工作得最好。世界各地的开发人员(包括来自BellSoft的开发人员)多年的生产都为上游做出了贡献,这意味着可能的安全问题被快速发现和修复,开发工具不断升级,新功能不断添加,过时的组件被移除。Java仍然是最好的多平台解决方案。因此,对于企业来说,使用Java是最可靠的选择,而且通常版本越新越好。如果您不确定是否需要更新,请与我们联系,BellSoft的工程师将检查您的架构,并分享他们对该问题的详细专家意见。

无论如何,Java都不会出错。

因此,请继续关注,因为我们即将发现最新版本中会发生什么变化。

时髦的17号

现代汽车应该没有旧的设计元素,没有不必要的零件来限制其速度,但仍然舒适和强大。JDK社区似乎也遵循这些准则。这将成为一个长期支持版本,这意味着它将在多年内保持行业标准,并在很长一段时间内接受更新。Java17有望在2026年之前收到更新。与LTS版本一样,有些东西被弃用,有些功能被添加,最有价值的组件得到升级。

让我们探讨一下Java 17所带来的所有变化。

新增的功能:

一些新的东西可以走得更快。

  • 特定于上下文的反序列化过滤器(JEP 415),用于通过JVM范围的过滤器工厂配置过滤器。开发人员可以为每个反序列化操作选择一个过滤器。Java 9中引入了反序列化过滤器技术,使应用程序和库代码能够在反序列化传入数据流之前验证它们。新的特定于上下文的方法通过提供从反序列化过程中删除潜在恶意代码的能力,将显著增强安全性。
  • 引入外部函数和内存API(JEP 412)以用一个优秀的纯Java开发模型取代Java本机接口(JNI)。它将提供更好的性能、在不同类型的外部内存上操作的方式,并通过默认禁用不安全操作提供更好的安全性。在未来,预计它将允许使用C以外的语言编写的外来函数。不过,JNI仍然可以使用函数,因此依赖它的应用程序仍将在Java17中运行。
  • 始终严格的浮点语义(JEP 306)将恢复到12之前的Java版本中。由于上世纪90年代x86体系结构的限制,决定更改默认浮点语义。自从CPU开始支持SSE2以来,这个问题早已得到解决,而使浮点运算始终严格将简化数字敏感库的开发,包括Java.lang.MathJava.lang.StrictMath
  • switch的模式匹配(JEP 406)作为预览实现,允许针对多个模式测试表达式,每个模式都有一个特定的操作。所有现有的开关表达式和语句仍将编译。介绍了两种新的模式。
  • 新的macOS渲染管道(JEP 382)将使用Apple Metal API,而不是旧的OpenGL。这一点尤其重要,因为苹果有可能从未来的macOS版本中删除OpenGL API。它将适合现有的Java2D管道模型,并提供至少与当前渲染管道相同或更好的性能。

删除的功能:

那些只会让你慢下来的老一套的花絮和零碎的东西将不得不扔掉。

  • 删除实验性AOT和JIT编译器(JEP 410)。提前(AOT)和准时(JIT)编译器在一段时间前作为实验添加到Java中,但结果并不流行。由于没有注意到它被排除在JDK 16之外,因此决定完全删除编译器,包括以下模块:JDK.aot(jaotc工具)、internal.vm.compiler(Graal编译器)和JDK.internal.vm.compiler.management(Graal MBean)。请注意,虽然Graal已从Java17中删除,但它仍然是许多功能的基本工具,因此它将继续开发,并包含在Liberica native Image Kit等本机映像的构建中。
  • 删除远程方法调用(RMI)激活机制(JEP407),但保留RMI的所有其余部分。激活机制已经过时,不再使用。
  • 不推荐删除小程序API(JEP 398)。由于所有的浏览器要么已经取消了对Java浏览器插件的支持,要么计划这样做,这个现在无用的API也必须取消。实际上,一些编译和使用过的应用程序仍然依赖于该API,这就是为什么API本身仍然存在于Java17中的原因。如果您的情况是这样,那么您可能需要坚持使用Java8(支持到2022年)或Java11(支持到2023年)。
  • 不推荐在将来的版本中删除Security Manager(JEP 411)。这个组件运行得很好,因为它可以追溯到Java1.0,主要用于保护客户端代码。开发人员现在需要调整现代安全解决方案来完成这项任务。问题是,当在Java18中实际删除组件时,仍然使用安全管理器的应用程序将需要大量的代码重写。由于java.security.manager系统属性的默认值现在为“disallow”,因此大多数调用system.setSecurityManager()的测试需要使用-Djava.security.manager=allow启动。

升级的功能:

一些有用的工具需要稍加打磨才能再次发光。

  • Vector API(JEP 414)在上一版本中作为孵化API引入,将根据开发人员的反馈进行增强,以获得更好的性能。它将使用英特尔的短向量数学库(SVML)在x64上支持字符操作、超越和三角lanewise操作,并更好地将字节向量转换为布尔数组和布尔数组。我们稍后将深入讨论此功能的含义。
  • 所有JDK内部(JEP403)的强封装,关键内部API除外。其想法是禁止通过单个命令行选项放松内部元素的强封装。这反过来会带来更好的安全性,并鼓励开发人员使用标准API而不是内部元素。最终,它将使升级到未来Java版本更加轻松。缺点是,如果现有的应用程序或框架依赖于内部API,则可能需要大量的代码重写,甚至选择不更新Java 17。请注意,sun.misc.Unsafereflect class以及add opens标志仍然有效,将来可能会使用。
  • Sealed类和接口(JEP 409),限制哪些其他类或接口可以扩展或实现它们。它们以前作为预览特性提供,让开发人员能够更好地控制负责实现他们创建的类的代码。
  • 将JDK移植到MacOS/AArch64(JEP391)。正如我们已经讨论过的,苹果的Mac电脑正在全面过渡到AARC64架构,因此该端口对于macOS来说是必要的。请注意,Linux和Windows AArch64端口已经可用,通过使用条件编译,可以重用大量现有代码。
  • 增强型伪随机数生成器(JEP 356)通过新的RandomGenerator接口和适用于所有RNG算法的统一API实现,包括三个已经实现的算法。

关于Vector API的几句话

为了了解Vector API实现的真正影响,让我们做一些研究。

下面的测试代码将计算输入参数数组上的sin(double)。此函数已作为x86_64的内部热点实现。问题是,当使用SVML时,它能工作得更快吗?我们将使用以下基于jmh的代码来比较使用和不使用Vector API时的“sin”计算:

/*
* Copyright (c) 2021, BELLSOFT. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*/

import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;

import jdk.incubator.vector.VectorSpecies;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.DoubleVector;

@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(3)
@State(Scope.Thread)

public class VectAPI {

    static final VectorSpecies<Double> SPECIES = DoubleVector.SPECIES_PREFERRED;

    @Param({"8", "16", "64", "256", "1024", "4096", "16384"})
    int size;

    double[] in;
    double[] out;

    @Setup(Level.Trial)
    public void initArrays() {
        in = new double[size];
        for (int i = 0; i < size; i++) {
            in[i] = i;
        }
        out = new double[size];
    }

    @Benchmark
    @Threads(1)
    public double[] scalarSin() {
        for (int i = 0; i < size; i++) {
            out[i] = Math.sin(in[i]);
        }
        return out;
}

@Benchmark
@Threads(1)
public double[] vectorSin() {
    int i = 0;
    int upperBound = SPECIES.loopBound(size);
    for (; i < upperBound; i += SPECIES.length()) {
        DoubleVector va = DoubleVector.fromArray(SPECIES, in, i);
        DoubleVector vb = va.lanewise(VectorOperators.SIN);
        vb.intoArray(out, i);
        }
    for (; i < size; i++) {
        out[i] = Math.sin(in[i]);
        }
    return out;
    }
}

在Xeon(R)Platinum 8268 CPU@2.90GHz上运行此基准测试将给出以下结果:

Benchmark               (size)          Mode       Cnt      Score            Error      Units
VectAPI.scalarSin       8               avgt       15       82.495 ±         1.274      ns/op
VectAPI.scalarSin       16              avgt       15       170.337 ±        1.346      ns/op
VectAPI.scalarSin       64              avgt       15       679.862 ±        7.345      ns/op
VectAPI.scalarSin       256             avgt       15       2700.459 ±       34.977     ns/op
VectAPI.scalarSin       1024            avgt       15       11511.768 ±      194.371    ns/op
VectAPI.scalarSin       4096            avgt       15       45569.034 ±      477.101    ns/op
VectAPI.scalarSin       16384           avgt       15       188526.742 ±     1008.427   ns/op
VectAPI.vectorSin       8               avgt       15       12.815 ±         0.284      ns/op
VectAPI.vectorSin       16              avgt       15       22.056 ±         0.788      ns/op
VectAPI.vectorSin       64              avgt       15       76.410 ±         1.329      ns/op
VectAPI.vectorSin       256             avgt       15       297.379 ±        3.158      ns/op
VectAPI.vectorSin       1024            avgt       15       1344.975 ±       51.351     ns/op
VectAPI.vectorSin       4096            avgt       15       5817.973 ±       55.963     ns/op
VectAPI.vectorSin       16384           avgt       15       24212.389 ±      915.261    ns/op

最后,VectAPI.vectorSin大约是VectAPI.scalarSin的8倍。在检查编译日志(-XX:+printcomilation)之后,我们发现使用了Double512Vector,正好是8个double。它看起来像线性可伸缩性。

让我们看看引擎盖下面。主要操作是“sin”计算:“va.lanewise(VectorOperators.sin)”用于向量大小写的代码。它最终被编译为对svml库的调用。libsvml.so随x86_64 jdk17一起提供。它包含针对不同向量大小的多个向量化函数的实现。C2编译器在libsvml可用的情况下插入对这些函数的调用,而不是默认的标量实现。

请注意,在不支持AVX512的旧系统上运行基准测试(或通过设置UseAVX=1UseAVX=2来模拟基准测试以使用128位或256位向量)会显示与向量大小减少成比例的更差基准测试分数,从而产生几乎完美的线性刻度。通过禁用UseVectorStubs来触发标量“sin”内在函数来禁用矢量化内在函数,由于额外的开销,显示的结果略慢于标量内在函数。

我们的测试表明,Vector API可以方便地使用SVML,并且工作速度很快。它适应目标CPU能力(在本例中,使用不同的向量大小,性能与之线性扩展)。使用矢量API周围的代码进行标量计算是可行的,但预计会有一些开销。

Java17新功能概述插图1

原文地址:https://bell-sw.com/announcements/2021/08/11/pedal-to-the-metal-java-17-tools-and-features-to-expect.md/