Java9~17各版本的特性

2021/10/28 Java

# 相关链接

# java9

OpenJdk9官网特性描述 (opens new window)

经过4次推迟,历经曲折的Java9最终在2017年9月21日发布,提供了超过150项新功能特性。

  • 模块化机制,可以对jar包级别访问权限进行精细化控制
  • 在模块化机制的基础之上,对JDK进行依赖梳理和拆分
  • jshell,交互式命令行
  • 匿名内部类支持泛型推断
  • try语句新特性
  • String存储结构改变,char[] 变为byte[]
  • 接口支持私有方法
  • Stream新增API
  • 更简单的方式创建只读集合
  • 新的HttpClient
  • JavaDoc支持HTML5
  • JS引擎升级(后续版本中已经删除JS引擎,由于GraalVM的存在)

# 模块化

通过在一个module中声明module-info.java来对这个module可以访问已经对外开放访问的能力进行控制。应用项目中不会加入这个控制,应用场景是提供功能的jar包,本身又是多模块的,希望可以控制各个模块的访问权限。

# jshell命令

像Python和Scala之类的语言早就有交互式编程环境REPL (read-evaluate-print-loop),以交互式的方式对语句和表达式进行求值。开发者只需要输入一些代码,就可以在编译前获得对程序的反馈。而之前的Java 版本要想执行代码,必须创建文件、声明类、提供测试方法方可实现。

# 匿名内部类支持泛型推断

//<>中不用再声明类型
Callable<String> task = new Callable<>() {
    @Override
    public String call() throws Exception {
        return "123";
    }
};
1
2
3
4
5
6
7

# try语句新特性

Closable的对象可以放在try()表达式外声明实例化。

InputStreamReader inputStream = new InputStreamReader(System.in);
OutputStreamWriter outputStream = new OutputStreamWriter(System.out);
//流可以放在外面声明实例化
try (inputStream; outputStream) {
    inputStream.read();
    outputStream.write(1);
} catch (Exception e) {
    e.printStackTrace();
}
1
2
3
4
5
6
7
8
9

# String存储结构改变,char[] 变为byte[]

主要目的是节省空间,通过coder变量来决定如何编码byte[]

public final class String
    implements java.io.SerializableComparable<String>CharSequence {

    /**
     * The value is used for character storage.
     *
     * @implNote This field is trusted by the VM, and is a subject to
     * constant folding if String instance is constant. Overwriting this
     * field after construction will cause problems.
     *
     * Additionally, it is marked with {@link Stable} to trust the contents
     * of the array. No other facility in JDK provides this functionality (yet).
     * {@link Stable} is safe here, because value is never null.
     */
    @Stable
    private final byte[] value;

    /**
     * The identifier of the encoding used to encode the bytes in
     * {@code value}. The supported values in this implementation are
     *
     * LATIN1
     * UTF16
     *
     * @implNote This field is trusted by the VM, and is a subject to
     * constant folding if String instance is constant. Overwriting this
     * field after construction will cause problems.
     */
    private final byte coder;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 接口支持私有方法

Java8中已经支持在接口中写默认方法和静态方法,因此接口在java9开始支持写私有方法也是可以预期的。

interface InterB {
    private void method() {

    }
    private static void methodStatic(){
        
    }
}
1
2
3
4
5
6
7
8

# Stream新API

@Test
public void testTakeWhile() {
    List<Integer> list = Arrays.asList(13474810);
    //当takeWhile条件满足时遍历,不满足时停止,丢弃后面的元素
    //奇数继续,遇到不是奇数时停止
    list.stream().takeWhile(n -> n % 2 == 1).forEach(System.out::println);
    //output: 1,3
}

@Test
public void testDropWhile() {
    List<Integer> list = Arrays.asList(13474810);
    //当dropWhile条件满足时丢弃该元素,并继续遍历,直到遇到第一个不满足条件的数据,保留该元素和后面所有元素
    //遇到奇数时丢弃,遇到第一个不是奇数时,保留该
    list.stream().dropWhile(n -> n % 2 == 1).forEach(System.out::println);
    //output: 4,7,4,8,10
}

@Test
public void testOfNullableStream() {
    //of方法获取流,允许元素中有多个null值
    Stream<Integer> stream1 = Stream.of(12null);
    //如果元素中只有一个null,会报空指针异常
//        Stream<Object> stream2 = Stream.of(null);
    //新API,可以创建空的stream
    Stream<Object> nullStream = Stream.ofNullable(null);
}

@Test
public void testNewIterator() {
    //java8生成0到9
    Stream.iterate(0, n -> ++n).limit(10).forEach(System.out::println);
    //第二个参数为退出迭代的条件,更加灵活
    //java9 生成0到9
    Stream.iterate(0, n -> n < 10, n -> ++n).forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 创建不可变集合

Java8中需要借助工具栏Collections中的方法来创建不可变结合,java9之后集合类自身提供of方法来创建不可变集合。这里注意Arrays.asList和List.of的区别。

@Test
public void testOf() {
    List<Integer> integerList = List.of(1234);
    System.out.println(integerList);
    Map<IntegerString> stringMap = Map.of(1"xiaobai"2"xiaohei");
    System.out.println(stringMap);
    //注意,通过of创建set的时候,如果有重复元素,会直接报错: IllegalArgumentException
//        Set<String> nameSet = Set.of("xiaobai", "xiaohei", "xiaohei");
//        System.out.println(nameSet);
}

/**
    * Arrays.asList与List.of的区别
    */
@Test
public void differenceOfArraysAndList() {
    //定长集合,长度不能改变,也就是说不能执行add操作,但是可以通过set(index, value)修改值
    List<Integer> list1 = Arrays.asList(123);
    //error!
//        list1.add(4);
    //ok!
    list1.set(15);

    //不可变集合,无法进行任何修改
    List<Integer> list2 = List.of(123);
    //error!
//        list2.add(4);
    //error!
//        list2.set(1, 5);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# java10

OpenJdk10官网特性描述 (opens new window)

2018年3月21日,Oracle官方宣布JAVA10正式发布。JAVA10 一共定义了109个新特性,其中包含JEP,对开发人员来说,真正的新特性也就一个,还有一些新的API和JVM规范以及JAVA语言规范上的改动。

  • 286:局部变量类型推断
  • 296:将 JDK 森林合并到单个存储库中
  • 304:垃圾收集器接口
  • 307:G1 的并行完整 GC
  • 310:应用程序类数据共享
  • 312:线程局部握手
  • 313:删除本机头生成工具 (javah)
  • 314:附加 Unicode 语言标签扩展
  • 316:替代内存设备上的堆分配
  • 317:基于 Java 的实验性 JIT 编译器
  • 319:根证书
  • 322:基于时间的发布版本控制

# 局部变量类型推断

在局部代码块中,对于可以在编译期确定的类型,可以使用var来定义。这个特性并不意味着java是弱类型的语言,仅是提供了更简洁的书写方式。对于编译期无法确定的类型,依然要写清楚类型。

# 不能推断的情况

仅声明变量的情况,不能类型推断

//使用var来作为变量的引用声明
var list = new ArrayList<>();

//以下为不可以声明为var的情况

//1.使用var必须要求变量必须初始化
//    var username;//编译错误

//2.不能给变量赋null值
//    var username = null;//编译错误

//3.lambda表达式不可以声明为var
//    var supplier = ()->Math.random();//编译错误

//4.方法引用不可以声明为var
//    var method = System.out::println;//编译错误

//5.数组静态初始化不可以声明为var
//    var array = {"a", "b"};//编译错误
String[] array1 = {"1""2"};

//6.类的成员变量不可以使用var类型推断
//7.所有参数声明,方法入参,返回值,构造方法参数都不可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 新增API copyOf

List<String> list = List.of("a""b""c");
List<String> copy = List.copyOf(list);
//true, 对于不可变集合的copyOf,返回的是同一个集合
System.out.println(list == copy);

List<Object> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
List<Object> newCopy = List.copyOf(arrayList);
//false, 对普通的集合copyOf,得到的是一个新的不可变集合
System.out.println(arrayList == newCopy);
List<Object> newCopy2 = List.copyOf(arrayList);
//false, 使用同一个集合多次copyOf,得到的是不同的不可变集合
System.out.println(newCopy == newCopy2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# java11

OpenJdk11官网特性描述 (opens new window)

2018年9月26日,Oracle官方发布JAVA11。 这是JAVA大版本周期变化后的第一个长期支持版本,官方支持到2026年。

  • 181:基于 Nest 的访问控制
  • 309:动态类文件常量
  • 315:改进 Aarch64 内部函数
  • 318:Epsilon:无操作垃圾收集器
  • 320:删除 Java EE 和 CORBA 模块
  • 321:HTTP 客户端(标准)
  • 323:本地变量语法LAMBDA参数
  • 324:与Curve25519密钥协商和Curve448
  • 327:Unicode的10
  • 328:飞行记录器
  • 329:ChaCha20和Poly1305加密算法
  • 330:启动单文件源代码程序
  • 331:低开销堆纹
  • 332:传输层安全性 (TLS) 1.3
  • 333:ZGC:可扩展的低延迟垃圾收集器(实验性)
  • 335:弃用 Nashorn JavaScript 引擎
  • 336:弃用 Pack200 工具和 API

# String API

//true, 空格,制表符,换行等都认为是空的
boolean blank = "\t \n".isBlank();
System.out.println(blank);

//strip()
String source = " \n www.howaric.cn \t";
System.out.println(source.strip());//前后去除
System.out.println(source.stripLeading());//去除开头
System.out.println(source.stripTrailing());//去除结尾

//repeat
String repeat = "howaric".repeat(3);
System.out.println(repeat);

//split a string by \n to a stream
Stream<String> lines = "a\nb\nc\n".lines();
System.out.println(lines.count());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意strip与trim的区别

  • trim()可以去除字符串前后的半角空白字符
  • strip()可以去除字符串前后的全角和半角空白字符

# Optional API

这里包含了从JDK9到JDK11中,Optional的一些新API。

Optional<Object> emptyOptional = Optional.empty();
//JDK11 新增判断value是否为null
//不再需要对isPresent使用取反符号判断为null
emptyOptional.isEmpty();

Optional<String> aOptional = Optional.of("A");
//JDK10 如果为null,抛出异常NoSuchElementException
String aValue = aOptional.orElseThrow();

Optional<Object> emptyOptional2 = Optional.empty();
//JDK9 比较方便替换if else的写法,更加简介优雅
emptyOptional2.ifPresentOrElse(v -> {
    //存在不为null
}() -> {
    //不存在,处理
});

Optional<Object> emptyOptional3 = Optional.empty();
//JDK9 value为null时可以通过or来提供新的optional
Optional<Object> bOptional = emptyOptional3.or(() -> Optional.of("B"));
System.out.println(bOptional.get());

Optional<String> cOptional = Optional.of("C");
//JDK9 通过optional获取只包含value的流
Stream<String> stream = cOptional.stream();
stream.forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 新的HTTP客户端转正

JAVA9开始引入一个处理HTTP请求的HTTPClient API,该API支持同步和异步,而在JAVA11中成为正式可用状态,并提供对WebSocket和HTTP2的支持。

# 提供简化的编译运行方式

直接使用java命令进行编译运行.java文件。

java Test.java
1

注意:

  1. 一个文件中有多个main方法,运行第一个;
  2. 不可以使用其他文件中定义的类;

# 提供实验性质的ZGC

下一代的GC,从逻辑上取消了分代模型,可以管理上T的堆空间。以控制响应时间为优先,相比G1牺牲较少的吞吐量,但是可以大大降低响应时间。

ZGC特点

  • 暂停时间不会超过10ms
  • 既能处理几百兆的小堆,也能处理几个T的大堆
  • 和G1相比,应用吞吐能力不会下降超过15%
  • 为未来的GC功能和利用colord指针以及Load Barriers优化奠定基础
  • 目前只支持64位系统

# java12

OpenJdk12官网特性描述 (opens new window)

2019年3月19日,java12正式发布。

  • 189:Shenandoah:一个低暂停时间的垃圾收集器(实验性)
  • 230:微基准套件
  • 325:switch表达式(预览)
  • 334:JVM 常量 API
  • 340:一个 AArch64 端口
  • 341:默认 CDS 档案
  • 344:G1 支持可中断的 Mixed GC
  • 346:及时从 G1 返回未使用的已提交内存

# JMH微基准测试套件

JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件。简单的来说就是基于方法层面的基准测试,精度可以达到微秒级。当你定位到热点方法,希望进一步优化方法性能的时候,就可以使用JMH对优化的结果进行量化的分析。

应用场景

  1. 想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
  2. 对比接口不同实现在给定条件下的吞吐量
  3. 查看多少百分比的请求在多长时间内完成

使用方式:增加jar包和插件

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.31</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.31</version>
</dependency>
1
2
3
4
5
6
7
8
9
10

示例

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class T1_TestJMH {

    //-XX:-EliminateLocks
    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder().include(T1_TestJMH.class.getName())
                .forks(1).build();
        new Runner(options).run();
    }

    @Benchmark
    public void testStringBuilder() {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            stringBuilder.append(i);
        }
        stringBuilder.toString();
    }

    @Benchmark
    public void testStringBuffer() {
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < 10000; i++) {
            stringBuffer.append(i);
        }
        stringBuffer.toString();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# switch语句新特性(预览)

目前switch语句的问题

  1. 匹配自上而下,若无break, 后面的case语句都会执行
  2. 不同的case语句定义的变量名不能重复
  3. 不能在一个case里写多个执行结果一致的条件
  4. 整个switch不能作为表达式返回值

示例

//目前的语法
private static void normalSwitch() {
    int month = 2;
    String season;
    switch (month) {
        case 12:
        case 1:
        case 2:
            season = "winter";
            break;
        case 3:
        case 4:
        case 5:
            season = "spring";
            break;
        case 6:
        case 7:
        case 8:
            season = "summer";
            break;
        case 9:
        case 10:
        case 11:
            season = "autumn";
            break;
        default:
            throw new RuntimeException("Bad input");
    }
    System.out.println(season);
}

//Java12引入的新语法,更加简洁
private static void newSwitch() {
    int month = 2;
    String season = switch (month) {
        case 1212 -> "winter";
        case 345 -> "spring";
        case 678 -> "summer";
        case 91011 -> "autumn";
        default -> throw new RuntimeException("Bad input");
    };
    System.out.println(season);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 紧凑型数字格式化

NumberFormat formatter = NumberFormat.getCompactNumberInstance(Locale.CHINESENumberFormat.Style.SHORT);
System.out.println(formatter.format(10_000));//1万
System.out.println(formatter.format(19_000));//2万
System.out.println(formatter.format(1000_000_000));//10亿
System.out.println(formatter.format(1L << 40));//1万亿
1
2
3
4
5

# String新API

# transform()

它提供的函数作为输入提供给特定的String实例,并返回该函数返回的输出。 可以连续调用,从而对一个字符串连续处理。

String result = "www."
        .transform(input -> input + "howaric")
        .transform(input->input+".cn");
System.out.println(result);//www.howaric.cn
1
2
3
4

# indent()

该方法允许我们调整String实例的缩进。

String result = "Java\nGolang".indent(4);
System.out.println(result);
//output:
    Java
    Golang
1
2
3
4
5

# Files新增文件对比API

Files.mismatch()对比两个文件的差异,返回从哪个字节开始出现了不一致。

FileWriter fileWriter = new FileWriter("/tmp/a.txt");
fileWriter.write("a");
fileWriter.write("b");
fileWriter.write("c");
fileWriter.close();
FileWriter fileWriterB = new FileWriter("/tmp/b.txt");
fileWriterB.write("a");
fileWriterB.write("1");
fileWriterB.write("c");
fileWriterB.close();
System.out.println(Files.mismatch(Path.of("/tmp/a.txt")Path.of("/tmp/b.txt")));
//返回 1,即从下标1处开始不一致
1
2
3
4
5
6
7
8
9
10
11
12

# 可中断的 G1 Mixed GC

当 G1 垃圾回收器的回收超过暂停时间的目标,新feature能中止垃圾回收过程。

G1是一个垃圾收集器,设计用于具有大量内存的多处理器机器。由于它提高了性能效率,G1垃圾收集器最终将取代 CMS垃圾收集器。该垃圾收集器设计的主要目标之一是满足用户设置的预期的 JVM 停顿时间。

G1 采用一个高级分析引擎来选择在收集期间要处理的工作量,此选择过程的结果是一组称为 GC 回收集(collection set( CSet ))的区域。一旦收集器确定了 GC 回收集 并且 GC 回收、整理工作已经开始,这个过程是without stopping的,即 G1 收集器必须完成收集集合的所有区域中的所有活动对象之后才能停止;但是如果收集器选择过大的 GC 回收集,此时的STW时间会过长超出目标pause time。

这种情况在mixed collections时候比较明显。这个特性启动了一个机制,当选择了一个比较大的collection set,Java 12 中将把 GC 回收集(混合收集集合)拆分为mandatory(必需或强制)及optional两部分(当完成mandatory的部分,如果还有剩余时间则会去处理optional部分)来将mixed collections从without stopping变为abortable,以更好满足指定pause time的目标。

# 及时从 G1 返回未使用的已提交内存

新功能使JVM能够在空闲时自动将 Java 堆内存返还给操作系统,这也是 Java 12 中的另外一项重大改进。

See More

目前 Java 11 版本中包含的 G1 垃圾收集器暂时无法及时将已提交的 Java 堆内存返回给操作系统。G1目 前只有在full GC或者concurrent cycle(并发处理周期)的时候才会归还内存,由于这两个场景都是G1极力避免的, 因此在大多数场景下可能不会及时归还committed Java heap memory给操作系统。除非有外部强制执行。

在使用容器环境时,这种不利之处特别明显。即使在JVM虚拟机不活动,但如果仍然使用其分配的内存资源,哪怕是其中的一小部分,G1 回收器也仍将保留所有已分配的 Java 堆内存。而这将导致用户需要始终为所有资源付费,哪怕是实际并未用到,而云提供商也无法充分利用其硬件。如果在此期间虚拟机能够检测到 Java 堆内存的实际使用情况,并在利用空闲时间自动将 Java 堆内存返还,则两者都将受益。

JDK12的这个特性新增了两个参数分别是G1 PeriodicGCInterval及G1 PeriodicGCSystemLoadThreshold,设置为0的话,表示禁用。

如果应用程序为非活动状态,在下面两种情况任何一个描述下,G1 回收器会触发定期垃圾收集:

  • 自上次垃圾回收完成以来已超过 G1PeriodicGCInterval ( milliseconds ), 并且此时没有正在进行的垃圾回收任务。如果 G1PeriodicGCInterval 值为零表示禁用快速回收内存的定期垃圾收集。

  • 应用所在主机系统上执行方法 getloadavg(),默认一分钟内系统返回的平均负载值低于 G1PeriodicGCSystemLoadThreshold指定的阈值,则触发full GC或者concurrent GC( 如果开启 G1PeriodicGCInvokesConcurrent ),GC之后Java heap size会被重写调整,然后多余的内存将会归还给操作 系统。如果 G1PeriodicGCSystemLoadThreshold 值为零,则此条件不生效。

# 支持UNICODE11

JDK 12版本包括对Unicode 11.0.0的支持。在发布支持Unicode 10.0.0的JDK 11之后,Unicode 11.0.0引入了以下JDK 12中包含的新功能:684 new characters, 11 new blocks. 7 new scripts。

684个新字符:

  • 66个表情符号字符(66 emoji characters)
  • Copyleft符号(Copyleft symbol)
  • 评级系统的半星(Half stars for rating systems)
  • 额外的占星符号(Additional astrological symbols)
  • 象棋中国象棋符号(Xiangqi Chinese chess symbols)

7个新脚本:

  • Hanifi Rohingya
  • Old Sogdian
  • Sogdian
  • Dogra
  • Gunjala Gondi
  • Makasar
  • Medefaidrin

11个新块,包括上面列出的新脚本的7个块和以下现有脚本的4个块:

  • 格鲁吉亚扩展(Georgian Extended)
  • 玛雅数字(Mayan Numerals)
  • 印度Siyaq数字(Indic Siyaq Numbers)
  • 国际象棋符号(Chess Symbols)

# java13

OpenJdk13官网特性描述 (opens new window)

2019年9月17日,java13正式发布。

  • 350:动态 CDS 档案
  • 351:ZGC:取消提交未使用的内存
  • 353:重新实现旧的 Socket API
  • 354:开关表达式(预览)
  • 355:文本块(预览)

# switch语句新特性(预览2)

增加关键字yield, 在switch中返回值。

int month = 2;
String season = switch (month) {
    case 12:
    case 1:
    case 2:
        yield "winter";
    case 3:
    case 4:
    case 5:
        yield "spring";
    case 6:
    case 7:
    case 8:
        yield "summer";
    case 9:
    case 10:
    case 11:
        yield "autumn";
    default:
        throw new RuntimeException("Bad input");
};
System.out.println(season);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 文本块(预览)

在Java中,通常需要使用String类型表达HTML,XML,SQL或JSON等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。文本块就是指多行字符串,例如一段格式化后的xml、json等。而有了文本块以后,用户不需要转义,Java能自动搞定。因此,文本块将提高Java程序的可读性和可写性。

String html = """
        <html>
            <body>
                <p>Hello, world</p>
            </body>
        </html>
        """;
System.out.println(html);

String json = """
        {
            "username":"xiaobai",
            "age":18
        }
        """;
System.out.println(json);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

文本块看似简单,但是有很多细节需要注意。

  1. block中不能含有未转义的字符

这里\会导致编译报错,需要使用\进行转义

String lines = """
        line1\ line2
        """;
System.out.println(lines);
1
2
3
4
  1. 编译器在编译时,会删除多余的空格

下面这段代码中,用.来表示代码中的的空格,而这些位置的空格就是多余的,会被删除。
这里要注意的是,删除多少个前面的空格,取决于结束符”””;前面有多少个空格或者空格数最少的一行的空格数,取两者中的更小值。

String html = """
..............<html>
.............. <body>
.............. <p>Hello, world</p>
.............. </body>
..............</html>
..............""";
1
2
3
4
5
6
7

多余的空格有事也会出现在每一行的结尾,这些空格都会被删除。

String html = """
..............<html>...
.............. <body>
.............. <p>Hello, world</p>....
.............. </body>.
..............</html>...
..............""";
1
2
3
4
5
6
7

# 重新实现旧版套接字API

全新实现的NioSocketImpl来替换JDK1.0的PlainSocketImpl。此实现与NIO实现共享相同的内部基础结构,并且与现有的缓冲区高速缓存机制集成在一起,因此不需要使用线程堆栈。除此之外,他还有一些其他更改,例如使用java.lang.ref.Cleaner机制关闭套接字,实现在尚未关闭的套接字上进行了垃圾收集,以及在轮询套接字处于非阻塞模式时处理超时操作等方法。

  • 它便于维护和调试,与 NewI/O (NIO) 使用相同的 JDK 内部结构,因此不需要使用系统本地代码。
  • 它与现有的缓冲区缓存机制集成在一起,这样就不需要为 I/O 使用线程栈。
  • 它使用 java.util.concurrent 锁,而不是 synchronized 同步方法,增强了并发能力。
  • 新的实现是Java 13中的默认实现,但是旧的实现还没有删除,可以通过设置系统属性jdk.net.usePlainSocketImpl来切换到旧版本。

# ZGC支持返回未使用的内存

注意,并非所有的场景都需要及时将多余的堆内存返还给操作系统。

这里有一些特定的应用场景

  1. 按需付费使用的容器环境;
  2. 应用程序可能长时间闲置,并且和很多其他应用共享和竞争资源的环境;
  3. 应用程序在执行期间有非常不同的堆空间需求,例如,可能在启动的时候所需的堆比稳定运行的时候需要更多 的堆内存。
实现细节

ZGC的堆由若干个Region组成,每个Region被称之为ZPage。每个Zpage与数量可变的已提交内存相关联。当ZGC压 缩堆的时候,ZPage就会释放,然后进入page cache,即ZPageCache。这些在page cache中的ZPage集合就表示没有使用部分的堆,这部分内存应该被归还给操作系统。回收内存可以简单的通过从page cache中逐出若干个选好的ZPage来实现,由于page cache是以LRU(Least recently used,最近最少使用)顺序保存ZPage的,并且按照尺寸(小,中,大)进行隔离,因此逐出ZPage机制和回收内存相对简单了很多,主要挑战是设计关于何时从page cache中逐出ZPage的策略。

一个简单的策略就是设定一个超时或者延迟值,表示ZPage被驱逐前,能在page cache中驻留多长时间。这个超时时间会有一个合理的默认值,也可以通过JVM参数覆盖它。Shenandoah GC用了一个类型的策略,默认超时时间是5分钟,可以通过参数-XX:ShenandoahUncommitDelay = milliseconds覆盖默认值。

像上面这样的策略可能会运作得相当好。但是,用户还可以设想更复杂的策略:不需要添加任何新的命令行选项。例如,基于GC频率或某些其他数据找到合适超时值的启发式算法。JDK13将使用哪种具体策略目前尚未确定。可能最初只提供一个简单的超时策略,使用-XX:ZUncommitDelay = seconds选项,以后的版本会添加更复杂、更智能的策略(如果可以的话)。

uncommit能力默认是开启的,但是无论指定何种策略,ZGC都不能把堆内存降到低于Xms。这就意味着,如果Xmx和Xms相等的话,这个能力就失效了。-XX:-ZUncommit这个参数也能让这个内存管理能力失效。

# java14

OpenJdk14官网特性描述 (opens new window)

2020年3月17日,java14正式发布。

  • 305:instanceof 的模式匹配(预览)
  • 343:包装工具(孵化器)
  • 345:G1 的 NUMA 感知内存分配
  • 349:JFR 事件流
  • 352:非易失性映射字节缓冲区
  • 358:有用的空指针异常
  • 359:记录(预览)
  • 361:开关表达式(标准)
  • 362:弃用 Solaris 和 SPARC 端口
  • 363:删除并发标记清除 (CMS) 垃圾收集器
  • 364:macOS 上的 ZGC
  • 365:Windows 上的 ZGC
  • 366:弃用 ParallelScavenge + SerialOld GC 组合
  • 367:删除 Pack200 工具和 API
  • 368:文本块(第二次预览)
  • 370:外部内存访问 API(孵化器)

# instanceof模式匹配增强(预览)

可以直接在模式匹配的括号内声明对应类型的局部变量。

public static String getIfString(Object obj) {
    if (obj instanceof String value) {
        return value.strip();
    } else {
        //value is unavailable here
        return "Not a string";
    }
}
1
2
3
4
5
6
7
8

# switch语句新特性(正式)

将java11,12,13的switch特性确定为正式版本。

# 文本块(第二次预览)

增加两个特性:

  1. 在一行的结尾增加\可以取消改行的换行符
String json = """
        {
            "username":"xiaobai",\
            "age":18
        }
        """;
System.out.println(json);
//output
{
    "username":"xiaobai""age":18
}
1
2
3
4
5
6
7
8
9
10
11
  1. 可以通过\s增加空格
String json = """
        {
            "username"\s:\s"xiaobai",
            "age"\s:\s18
        }
        """;
System.out.println(json);
//output
{
    "username" : "xiaobai""age" : 18
}
1
2
3
4
5
6
7
8
9
10
11
12

# 新class类型record

Record是java的一种新的类型,同枚举一样,Record也是对类的一种限制,Record放弃了类通常享有的特性,将API和表示解耦,但是作为回报,record使数据类型变得非常简洁,一般可以帮助我们定义一些简单的用于传递数据的实体类。

record类的特点

  • 状态声明中的每个成员,都是一个private final的字段,属性设置值则不可修改
  • 状态声明中的每个组件的公共读取访问方法,该方法和组件具有相同的名字,get方法和属性名一致
  • equals,hashcode,toString的默认实现
  • record提供的默认是一个全参的构造器
  • records类是隐含的final类,并且不是抽象类,records不能拓展任何类,不能被继承,声明的任何其他字段都必须是静态的
public static void main(String[] args) {
    User user = new User("xiaobai"18);
    System.out.println(user.username());
    System.out.println(user.age());
    System.out.println(user);
}

record User(String username, int age) {
}

//ouput
xiaobai
18
User[username=xiaobai, age=18]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 友好的空指针提示

Java14在空指针异常的时候可以打印更加明确的信息了。

public static void main(String[] args) {
    Dog dog = new Dog(null);
    dog.leg.walk();
}

static class Dog {
    Leg leg;

    public Dog(Leg leg) {
        this.leg = leg;
    }
}

static class Leg {
    void walk() {
        System.out.println("Walk!");
    }
}
// output
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.github.howaric.java17.java14.T4_TestNullPointException$Leg.walk()" because "dog.leg" is null
at com.github.howaric.java17.java14.T4_TestNullPointException.main(T4_TestNullPointException.java:7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 打包工具

该特征旨在创建一个用于打包独立java应用程序的工具,jpackage工具可以将java的应用程序打包到特定的平台的程序包中,该程序包包含所必须的依赖。

该应用程序可以作为普通的jar文件或者模块的集合提供,受支持的特定平台的软件包格式为:

  1. Linux:deb或rpm
  2. maxOS:pkg或dmg
  3. windows:msi或exe

默认情况下,jpackage以最适合其运行系统的格式生成软件包。

如果有一个包含jar文件的应用程序,所有的应用程序都位于一个名为lib 的目录总,并且lib/main.jar包含主类,可以通过如下命令打包。

jpackage --name myapp -- input lib --main-jar main.jar
1

如果MANIFEST.MF文件中没有main.jar.没有Main-Class属性,则必须显式指定主类。

jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main
1

# G1的NUMA内存分配优化

这个特性希望通过实现NUMA-aware的内存分配,改进G1在大型机上的性能。

NUMA是非统一内存访问架构(英语:non-uniform memory access,简称NUMA),是一种为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。在NUMA下,处理器访问它自己的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。

被共享的内存物理上是分布式的,所有这些内存的集合就是全局地址空间。所以处理器访问这些内存的时间是不一样的,显然访问本地内存的速度要比访问全局共享内存或远程访问外地内存要快些。另外,NUMA中内存可能是分层的:本地内存,群内共享内存,全局共享内存。

# JFR事件流

Java Flight Recorder(JFR)是JVM的诊断和性能分析工具。

JAVA14之前只能做离线的分析,现在可以做实时的持续监视。它可以收集有关JVM以及在其上运行的Java应用程序的数据。JFR是集成到JVM中的,所以JFR对JVM的性能影响非常小,我们可以放心的使用它。

一般来说,在使用默认配置的时候,性能影响要小于1%。

扩展阅读

JFR是JVM的调优工具,通过不停的收集JVM和java应用程序中的各种事件,从而为后续的JMC分析提供数据。

Event是由三部分组成的:时间戳,事件名和数据。同时JFR也会处理三种类型的Event:持续一段时间的Event,立刻触发的Event和抽样的Event。为了保证性能的最新影响,在使用JFR的时候,请选择你需要的事件类型。

JFR从JVM中搜集到Event之后,会将其写入一个小的thread-local缓存中,然后刷新到一个全局的内存缓存中,最后将缓存中的数据写到磁盘中去。或者可以配置JFR不写到磁盘中去,但是这样缓存中只会保存部分events的信息。

在JDK14 JEP 349提供的JFR事件流中,我们可以监听Event的变化,从而在程序中进行相应的处理。这样不需要生成JFR文件也可以监听事件变化。

public static void main(String[] args) throws IOExceptionParseException {
    //default or profile 两个默认的profiling configuration files
    Configuration config = Configuration.getConfiguration("default");
    try (var es = new RecordingStream(config)) {
        es.onEvent("jdk.GarbageCollection"System.out::println);
        es.onEvent("jdk.CPULoad"System.out::println);
        es.onEvent("jdk.JVMInformation"System.out::println);
        es.setMaxAge(Duration.ofSeconds(10));
        es.start();
    }
}
//output
jdk.JVMInformation {
  startTime = 11:43:05.322
  jvmName = "OpenJDK 64-Bit Server VM"
  jvmVersion = "OpenJDK 64-Bit Server VM (17.0.1+12-39) for bsd-amd64 JRE (17.0.1+12-39), built on Sep 27 2021 17:37:19 by "mach5one" with clang Apple LLVM 12.0.0 (clang-1200.0.32.29)"
  jvmArguments = "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=58565:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8"
  jvmFlags = N/A
  javaArguments = "com.github.howaric.java17.java14.T5_TestJRF"
  jvmStartTime = 11:43:04.728
  pid = 15350
}

jdk.GarbageCollection {
  startTime = 11:43:06.264
  duration = 5.85 ms
  gcId = 0
  name = "G1New"
  cause = "G1 Evacuation Pause"
  sumOfPauses = 5.85 ms
  longestPause = 5.85 ms
}

jdk.CPULoad {
  startTime = 11:43:07.249
  jvmUser = 2.12%
  jvmSystem = 0.17%
  machineTotal = 18.85%
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

JDK Mission Control 是一个用于对 Java 应用程序进行管理、监视、概要分析和故障排除的工具套件,配套JFR的文件进行分析。在JDK14中,JMC是独立于JDK单独发行的。我们可以下载之后进行安装。

# 外部存储API(孵化)

通过一个API,可以允许java程序安全有效地访问Java堆之外的存储空间,此外部内存API旨在替代当前使用的方法(java.nio.ByteBuffer和sun.misc.Unsafe)。

目前要访问堆内存之外的外部存储是,通常有两种方式:

  1. java.nio.ByteBuffer:ByteBuffer允许使用allcateDirect()方法在堆内存之外分配内存空间;
  2. sum.misc.Unsafe: Unsafe中的方法可以直接对内存地址进行操作;

这两种方式存在的问题

ByteBuffer有自己的限制。首先是ByteBuffer的大小不能超过2G,其次是内存的释放依靠垃圾回收器。Unsafe的API在使用是不安全的,风险很高,可能会造成JVM崩溃。另外Unsafe本身是不被支持的API,并不推荐。

目前该API还是属于孵化阶段,相关API在jdk.incubator.foreign模块的jdk.incubator.foreign包中,三个API分别是:MemorySegment,MemoryAddress和MemoryLayout。MemorySegment用于对具有给定空间和时间范围的连续内存区域进行建模。可以将MemoryAddress视为段内的偏移量。最后,MemoryLayout是内存段内容的程序化描述。

# java15

OpenJdk15官网特性描述 (opens new window)

2020年9月15日,java15正式发布。

  • 339:爱德华兹曲线数字签名算法 (EdDSA)
  • 360:密封类(预览)
  • 371:隐藏类
  • 372:删除 Nashorn JavaScript 引擎
  • 373:重新实现旧版 DatagramSocket API
  • 374:禁用和弃用偏向锁定
  • 375:instanceof 的模式匹配(第二次预览,无改动)
  • 377:ZGC:可扩展的低延迟垃圾收集器(确定正式版)
  • 378:文本块(确定正式版)
  • 379:Shenandoah:一个低暂停时间的垃圾收集器(确定正式版)
  • 381:删除 Solaris 和 SPARC 端口
  • 383:外内存访问API(第二孵化器)
  • 384:记录(第二次预览)
  • 385:弃用 RMI 激活以进行删除

# 密封类(预览)

密封的类和接口用于限制超类的使用,密封的类和接口限制其他可继承或者实现他们的其他类或接口。

语法

引入 Seald class或interface,这些class或者interface只允许被指定的类或者interface进行扩展和实现。

使用修饰符sealed,我们可以将一个类声明为密封类。密封类使用reserved关键字permits列出可以直接扩展他的类。子类可以是final的,非密封或者密封的。

密封类

//密封必须被继承,且可以使用permits来进行指定可以被哪些类继承
sealed class Animal permits DogCatFish {
}
//一个密封类的子类必须是final,sealed或non-sealed的
final class Dog extends Animal {
}
//如果子类是sealed,那么这个类也一样要被继承,否则编译错误
sealed class Fish extends Animal {
}
final class Tuna extends Fish {
}
//密封类的子类也可以是非密封的,那么这个类没有被继承的硬性要求,也不能使用permits
non-sealed class Cat extends Animal {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

密封接口

//sealed的接口要被继承或者实现
sealed interface InterA permits InterBInterC {
}
//继承sealed的接口也要是sealed或non-sealed
sealed interface InterB extends InterA {
}
//如果是sealed,那么需要被实现或者继续被继承
final class BImpl implements InterB {
}
//non-sealed的接口继承sealed的接口,终止密封的传递
non-sealed interface InterC extends InterA {
}
1
2
3
4
5
6
7
8
9
10
11
12

sealed与record

sealed interface People {
    void eat();
}
//record本身就是隐式final的,因此不需要final修饰
record Student(String username, int age) implements People {
    @Override
    public void eat() {
        System.out.println("student eat!");
    }
}
1
2
3
4
5
6
7
8
9
10

# 隐藏类

该提案通过启用标准API来定义无法发现且有有限生命周期的隐藏类,从而提高JVM上所有语言的效率. JDK内部和外部的框架将能够动态生成类,而这些类可以定义隐藏类。通常来说基于JVM的很多语言都有动态生成类的机制,这样可以提高语言的灵活性和效率。

  • 隐藏类天生为框架设计的,在运行时生成内部的class;
  • 隐藏类只能通过反射访问,不能直接被其他类的字节码访问;
  • 隐藏类可以独立于其他类加载,卸载,这样可以减少框架的内存占用;

Hidden Class就是不能直接被其他class的二进制代码使用的class。主要被一些框架用来生成运行时类,但是这些类不能被用来直接使用的,是通过反射来调用的。比如JDK8中引入的lambda表达式,编译时不会将lambda表达式转换为专门的类,而是在运行时将相应的字节码动态生成相应的类对象。另外使用动态代理也可以为某些类生成新的动态类。

扩展阅读

隐藏类需要的特征

  1. 不可发现性:因为我们是为某些静态的类动态生成的动态类,所以我们希望这个动态生成的类看作是静态类的一部分,所以我们不希望除了该静态类以外的其他机制发现
  2. 访问控制:我们希望在访问控制静态类的同时,也能控制到动态生成的类
  3. 生命周期:动态生成类的声明周期一般都比较短. 我们不需要将其保存和静态类的生命周期一致

API支持

  • java.lang.reflect.Proxy 可以定义隐藏类作为实现代理接口的代理类
  • java.lang.invoke.StringConcatFactory可以生成隐藏类来保存常量连接方法
  • java.lang.invoke.LambdaMetaFactory可以生成隐藏的nestmate类,以容纳访问封闭变量的lambda主体

普通类是通过调用ClassLoader::defineClass创建的,而隐藏类是通过调用Lookup::defineHiddenClass创建的,这使JVM提供的字节派生一个隐藏类,链接该隐藏类,并返回提供对隐藏类的反射访问的查找对象,调用程序可以通过返回的查找对象来获取隐藏类的Class对象。

# ZGC确定为正式版

ZGC是JAVA11 引入的新的垃圾收集器,经历了多个阶段,自从终于成正式特性自2008年以来,ZGC已经增加了许多改进,并发类卸载,取消未使用的内存,对类数据实现共享的支持到NUMA感知,此外,最大的堆从4T增加到了16T,支持平台包括Linux,Windows和MacOS .ZGC 是一个重新设计的并发垃圾收集器,通过GC停顿时间来提高性能,但是这并不是替换默认的G1垃圾收集器。

启用参数:-XX:+UseZGC

# ShenandoahGC确定为正式版

这是一个JAVA12引入的垃圾回收器,该算法通过正在运行的JAVA线程同时进行疏散工作来减少GC暂停时间。Shenandoah的暂停时间与堆大小无关,无论是200M还是200G,都具有机会一致的暂停时间。

启用参数:-XX:+UseShenandoahGC

Shenandoah 和ZGC 对比

  • 相同:性能几乎认为是相同的;
  • 不同:算法不同;ZGC是OracleJDK的, 而Shenandoah只存在于OpenJDK中;

# java16

OpenJdk16官网特性描述 (opens new window)

2021年3月16日,java16正式发布。

  • 338:Vector API(孵化器)
  • 347:启用 C++14 语言功能
  • 357:从 Mercurial 迁移到 Git
  • 369:迁移到 GitHub
  • 376:ZGC:并发线程栈处理
  • 380:Unix 域套接字通道
  • 386:Alpine Linux 端口
  • 387:弹性元空间
  • 388:Windows/AArch64 端口
  • 389:外链 API(孵化器)
  • 390:基于值的类的警告
  • 392:打包工具
  • 393:外内存访问API(第三孵化器)
  • 394:instanceof 的模式匹配
  • 395:记录
  • 396:默认情况下强封装JDK内部
  • 397:密封类(第二次预览)

# instanceof模式匹配

在Java14的基础上,增加新的改动。

  1. 取消模式变量是隐式 final 的限制,以减少局部变量和模式变量之间的不对称性。
  2. 将instanceof类型S的表达式与类型T的模式进行比较,使模式表达式成为编译时错误,其中S是T的子类型。
public static void main(String[] args) {
    String value = "123";
    if (value instanceof String str) {//编译错误

    }
    B b = new B();
    if (b instanceof A bb) {//编译错误

    }
}

static class A {
}

static class B extends A {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Vector API(孵化)

jdk.incubator.vector来表达向量计算在运行时可靠地编译到最佳矢量的硬件指令上支持的CPU架构,从而实现优异的性能等效标量计算。这种显式矢量化 API 可能适用于许多领域,例如机器学习、线性代数、密码学、金融和 JDK 本身的用法。

设计目标
  • 清晰简洁的 API: API 应能够清晰简洁地表达广泛的矢量计算,这些矢量计算由一系列矢量操作组成,这些矢量操作通常在循环内组成,可能还有控制流。应该可以表达对向量大小(或每个向量的车道数)通用的计算,从而使此类计算能够在支持不同向量大小的硬件之间移植(如下一个目标中详述)。

  • 平台不可知: API 应与体系结构无关,支持在支持向量硬件指令的多个 CPU 体系结构上的运行时实现。与平台优化和可移植性冲突的 Java API 中的常见情况一样,偏向于使 Vector API 具有可移植性,即使某些特定于平台的习语不能直接用可移植代码表达。x64 和 AArch64 性能的下一个目标代表了支持 Java 的所有平台上的适当性能目标。在ARM可缩放矢量扩展(SVE)在这方面特别关注,以确保API能支持这种架构,即使是写没有已知的生产硬件实现的。

  • 在 x64 和 AArch64 架构上可靠的运行时编译和性能: Java 运行时,特别是 HotSpot C2 编译器,应在有能力的 x64 架构上将向量操作序列编译为相应的向量硬件指令序列,例如Streaming SIMD支持的那些 扩展(SSE) 和高级矢量扩展 (AVX) 扩展,从而生成高效和高性能的代码。程序员应该相信他们表达的向量操作将可靠地映射到相关的硬件向量指令。这同样适用于编译为Neon支持的向量硬件指令序列的有能力的 ARM AArch64 架构。

  • 优雅降级: 如果向量计算无法在运行时完全表示为硬件向量指令序列,要么是因为架构不支持某些所需指令,要么是因为不支持另一种 CPU 架构,那么 Vector API 实现应优雅降级并且仍然起作用。这可能包括如果矢量计算无法充分编译为矢量硬件指令,则向开发人员发出警告。在没有向量的平台上,优雅降级将产生与手动展开循环竞争的代码,其中展开因子是所选向量中的通道数。

# ZGC:并发线程栈处理

将ZGC线程栈处理从安全点移动到并发阶段。

目标

  1. 从 ZGC 安全点中删除线程堆栈处理。
  2. 使堆栈处理变得懒惰、协作、并发和增量。
  3. 从 ZGC 安全点中删除所有其他每线程根处理。
  4. 提供一种机制,其他 HotSpot 子系统可以通过该机制延迟处理堆栈。

ZGC 垃圾收集器旨在使 HotSpot 中的 GC 暂停和可扩展性问题成为过去。到目前为止,我们已经将所有随堆大小和元空间大小扩展的 GC 操作从安全点操作移到并发阶段。这些包括标记、重定位、引用处理、类卸载和大多数根处理。

仍然在 GC 安全点中完成的唯一活动是GCRoot处理的子集和有时间限制的标记终止操作。根包括 Java 线程堆栈和各种其他线程根。这些根是有问题的,因为它们会随着线程的数量而扩展。由于大型机器上有许多线程,根处理成为一个问题。

为了超越我们今天所拥有的,并满足在 GC 安全点内花费的时间不超过一毫秒的期望,即使在大型机器上,我们也必须将这种每线程处理,包括栈扫描,移到并发阶段。

在这项工作之后,在 ZGC 安全点操作中基本上不会做任何重要的事情。

作为该项目的一部分构建的基础设施最终可能会被其他项目使用,例如 Loom 和 JFR,以统一延迟堆栈处理。

# Unix 域套接字通道

将 Unix 域 ( AF_UNIX) 套接字支持添加到包中的套接字通道和服务器套接字通道API java.nio.channels。扩展继承的通道机制以支持 Unix 域套接字通道和服务器套接字通道。

unix域套接字

Unix 域套接字用于同一主机上的进程间通信 (IPC)。它们在大多数方面类似于 TCP/IP 套接字,不同之处在于它们由文件系统路径名而不是 Internet 协议 (IP) 地址和端口号寻址。此 JEP 的目标是支持在主要 Unix 平台和 Windows 中通用的 Unix 域套接字的所有功能。Unix 域套接字通道在读/写行为、连接设置、服务器对传入连接的接受、与选择器中的其他非阻塞可选通道的多路复用以及相关套接字的支持方面的行为与现有的 TCP/IP 通道相同选项。

对于本地、进程间通信,Unix 域套接字比 TCP/IP 环回连接更安全、更高效。

Unix 域套接字严格用于同一系统上的进程之间的通信。不打算接受远程连接的应用程序可以通过使用 Unix 域套接字来提高安全性。

  • Unix 域套接字受到操作系统强制的、基于文件系统的访问控制的进一步保护。
  • Unix 域套接字比 TCP/IP 环回连接具有更快的设置时间和更高的数据吞吐量。
  • 对于需要在同一系统上的容器之间进行通信的容器环境,Unix 域套接字可能是比 TCP/IP 套接字更好的解决方案。这可以使用位于共享卷中的套接字来实现。

# 弹性元空间

更及时地将未使用的 HotSpot 类元数据(即元空间)内存返还给操作系统,减少元空间占用空间,并简化元空间代码以降低维护成本。

# 外部链接器 API(孵化)

介绍一个 API,它提供对本机代码的静态类型、纯 Java 访问。此 API 与外部内存 API ( JEP 393 ) 一起,将大大简化绑定到本机库的其他容易出错的过程。

扩展阅读

目标

  • *易用性:*用卓越的纯 Java 开发模型替换 JNI。
  • C 支持: 这项工作的初始范围旨在在 x64 和 AArch64 平台上提供与 C 库的高质量、完全优化的互操作性。
  • *通用性:*外部链接器 API 和实现应该足够灵活,随着时间的推移,可以适应其他平台(例如,32 位 x86)和用非 C 语言编写的外部函数(例如 C++、Fortran)。
  • *性能:*外部链接器 API 应提供与 JNI 相当或优于 JNI 的性能。

原因

从 Java 1.1 开始,Java 就支持通过Java 本地接口 (JNI)调用本地方法,但这条路径一直是艰难而脆弱的。使用 JNI 包装本机函数需要开发多个工件:Java API、C 头文件和 C 实现。即使有工具帮助,Java 开发人员也必须跨多个工具链工作,以保持多个依赖于平台的工件同步。这对于稳定的 API 来说已经够难了,但是当试图跟踪正在进行的 API 时,每次 API 发展时更新所有这些工件是一个重大的维护负担。最后,JNI 主要是关于代码的,但代码总是交换数据,而 JNI 在访问本机数据方面提供的帮助很小。出于这个原因,开发人员经常求助于解决方法(例如直接缓冲区或sun.misc.Unsafe) 这使得应用程序代码更难维护,甚至更不安全。

多年来,出现了许多框架来填补 JNI 留下的空白,包括JNA、JNR和JavaCPP。JNA 和 JNR 从用户定义的接口声明动态生成包装器;JavaCPP 生成由 JNI 方法声明上的注释静态驱动的包装器。虽然这些框架通常比 JNI 体验有显着改进,但情况仍然不太理想,尤其是与提供一流的本地互操作的语言相比时。例如,Python 的ctypes包可以在没有任何胶水代码的情况下动态包装本机函数。其他语言,例如Rust,提供了从 C/C++ 头文件机械地派生本机包装器的工具。

最终,Java 开发人员应该能够(大部分)只使用任何被认为对特定任务有用的本地库——我们已经看到现状如何阻碍实现这一目标。此 JEP 通过引入高效且受支持的 API — 外部链接器 API — 来纠正这种不平衡,该 API 提供外部函数支持,而无需任何干预 JNI 胶水代码。它通过将外部函数公开为可以在纯 Java 代码中声明和调用的方法句柄来实现这一点。这大大简化了编写、构建和分发依赖于外部库的 Java 库和应用程序的任务。此外,Foreign Linker API 与 Foreign-Memory Access API 一起,为第三方本机互操作框架(无论是现在还是未来)都可以可靠地构建提供了坚实而高效的基础。

# java17

OpenJdk17官网特性描述 (opens new window)

2021年9月14日,java17正式发布。长期支持版,支持到2029年。
Oracle 宣布,从 JDK 17 开始,后面的 JDK 都全部免费提供。

  • 306:恢复始终严格的浮点语义
  • 356:增强型伪随机数发生器
  • 382:新的 macOS 渲染管线
  • 391:macOS/AArch64 端口
  • 398:弃用 Applet API 以进行删除
  • 403:强封装JDK内部
  • 406:switch模式匹配(预览)
  • 407:删除 RMI 激活
  • 409:密封类(正式确定)
  • 410:删除实验性 AOT 和 JIT 编译器
  • 411:弃用安全管理器以进行删除
  • 412:外部函数和内存 API(孵化器)
  • 414:Vector API(第二孵化器)
  • 415:上下文特定的反序列化过滤器

# switch模式匹配(预览)

使用switch 表达式和语句的模式匹配以及对模式语言的扩展来增强 Java 编程语言。扩展模式匹配以switch允许针对多个模式测试表达式,每个模式都有特定的操作,以便可以简洁安全地表达复杂的面向数据的查询。

public static String testOld(Object o) {
    String result = "unknown";
    if (o instanceof Integer i) {
        result = "int: " + i;
    } else if (o instanceof Long l) {
        result = "long: " + l;
    } else if (o instanceof String str) {
        result = "string: " + str;
    } else {
        return o.toString();
    }
    return result;
}

public static String testNew(Object o) {
    return switch (o) {
        case Integer i -> "int: " + i;
        case Long i -> "long: " + i;
        case String s -> "string: " + s;
        default -> o.toString();
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 总结

  • 语法更加简洁(switch,instanceof,record)
  • 对封装继承更加精细的控制(sealed)
  • 内存优化,尽量少的使用主机内存,并且尽可能及时地将多余的JVM内存返还给操作系统
  • 延迟更低的GC(ZGC,Shenandoah)
  • 正在实现协程(Loom)
  • 更好地支持容器(容器内获取限制的cpu,alpine,返还内存,uninx套接字)
  • 提供正式友好的API访问存储(MemoryAddress)
  • 提供更加简单的API来调用本地静态库