新版本jdk(9、11、12、13、14)特性

目录

 

背景

jdk9新特性

目录结构的改变

模块化系统

要解决的问题

概念

实现目标

示例

jShell命令

多版本兼容jar包

接口中的私有方法

钻石操作符(泛型)的升级

 try语句的升级

下划线命名标识符的限制

String存储结构的变化

快速创建只读集合

增强的流api

takeWhile

dropWhile

of()方法和ofNullable()方法

iterator()重载方法

Optional类中的stream()方法

多分辨率图像api

全新的http客户端api

被废弃的api

智能java编译工具sjavac

统一的jvm日志系统

javadoc的H5支持

js的Nashorn引擎升级

动态编译器

总结

jdk11新特性

局部变量自动类型推断

String类新增方法

Optional方法增强

transferTo()方法

移除和废弃的一些内容

更简化的编译运行程序

Unicode10

新增的Epsilon收集器

ZGC

完全支持linux容器(包括docker)

G1的完全并行GC

免费的低耗能分析(Low-Overhead Heap Profiling)

新的加密算法

Flight Recorder

jdk12新特性

新的Shenandoah(雪兰多)收集器

微基准表达式

switch表达式

JVM常量api

只保留一个AArch64实现

默认类数据共享归档文件

可中止的G1混合收集器

G1及时返回未使用的已分配内存

String中新增的方法

transform()方法

indent()方法

Files中新增的方法——mismatch()

其他新增项

jdk13新特性

动态CDS档案

ZGC:提交未使用的堆内存

重新实现旧版套接字api

switch表达式中引入yield

文本块

新增项

总结

jdk14新特性

instanceof省去了强制类型转换的过程

更详细的空指针异常

Record数据结构

jdk12/13中的switch表达式正式转正

增强版的文本块

弃用Parallel Scavenge和Serial Old的GC组合

windows和mac上可用ZGC

结语


背景

熬夜看完谢菲联3:1战胜热刺,曼城4:0横扫已经提前夺冠的红军,醒来又是无聊的一天........

jdk9新特性

目录结构的改变

jdk8及以前的目录结构如下所示(MemoryAnalyzer是我后来加的,不属于原有的结构)

jdk9的目录结构如下图所示,其中jre目录是后来加的,也就是原有jdk9目录不包含jre

模块化系统

要解决的问题

jre的膨胀和臃肿(每次JVM启动时,由于要加载rt.jar,所以要至少加载30~60MB的内存);不同版本的类库交叉依赖;每个公共类可以被类路径下任何其他的公共类所访问到,会导致使用并不想开放的api;类路径可能重复等

概念

模块实际上就是包外面再包一层,来管理包,使代码更安全

我们可以使用jlink工具,定制运行时环境

实现目标

模块独立、化繁为简

减少内存开销;只加载必要jdk模块,简化类库和大型应用的开发维护;改进java SE,使其适应不同大小的计算设备;改进安全性

示例

1)、创建项目,基于jdk9

2)、构建两个模块,demo0和demo1,demo1中引用demo0中的bean类

3)、点击Project Structure->Modules,把两个模块和项目的语言等级都设置成9,并把src目录标记为源目录

4)、点击设置->构建、执行、部署->编译器->java编译器,把三个部分的字节码版本也都设置成9

5)、在demo0的源目录下创建module-info.java文件,声明要导出的包

内容如下

module demo0 {
    exports com.szc.bean; // 导出包
}

6)、在demo1的源目录下也创建module-info.java文件,声明要引用的模块

内容如下

module demo1 {
    requires demo0;
}

然后就可以在demo1中使用demo0中导出包的内容了

import com.szc.bean.Person;


public class ModuleTest {
    public static void main(String[] args) {
        Person p = new Person("szc", 23);
    }
}

而没有暴露的包,里面的类就不能被引用,例如下图中entity包下的User类

在demo1中强行使用会报错

7)、我们当然也可以使用第三方的模块,比如java.logging,在demo1中的module-info.java引入此包即可

module demo1 {
    requires demo0;
    requires java.logging;
}

然后,就能使用了

import com.szc.bean.Person;


import java.util.logging.Logger;


public class ModuleTest {
    private static final Logger sLOGGER = Logger.getLogger("szc");


    public static void main(String[] args) {
        Person p = new Person("szc", 23);


        sLOGGER.info(p.toString());
    }
}

运行输出如下

jShell命令

提供类似python、scala的repl交互式编程环境

我们可以进行输出语句、定义变量等操作

也可以定义方法,并调用

以及方法的重载

还可以导包,显示导入的包

我们按/+tab可以显示所有可用的命令

其中/list可以显示输入历史

/edit可以调出编辑面板

面板中三个按钮自然表示取消、提交和退出编辑面板

jshell中没有受检异常,也就是自行捕获编译期异常

最后,ctrl-c退出,或者/exit退出

多版本兼容jar包

此机制可以让我们创建仅在某个版本的java环境中的class版本,也可以让class的后期版本直接覆盖前期版本

接口中的私有方法

jdk7中只能声明全局常量和抽象方法

interface IMyInterface {
    public void test0();
}

jdk8中的接口可以有具体的静态方法和默认方法

interface IMyInterface {

    public static void test1 () {
        System.out.println("静态方法");
    }


    default void test2() { // 可以被实现类覆写
        System.out.println("默认方法");
    }


    public void test0();
}

jdk9中的接口甚至私有方法都能有方法体,而jdk8及以前接口方法都是公有的

interface IMyInterface {
    private void test() {
        System.out.println("接口中的私有方法");
    }


    public static void test1 () {
        System.out.println("静态方法");
    }


    default void test2() {
        System.out.println("默认方法");
    }


    public void test0();
}

钻石操作符(泛型)的升级

创建泛型类对象时,可以覆写其中的方法

Set<String> set = new HashSet<>(){
    @Override
    public boolean add(String s) {
        return super.add(s + "..");
    }
};

测试代码

public class DiamondOperatorTest {
    public static void main(String[] args) {
        new DiamondOperatorTest().test();;
    }

    public void test() {
        Set<String> set = new HashSet<>(){
            @Override
            public boolean add(String s) {
                return super.add(s + "..");
            }
        };


        set.add("1");
        set.add("2");
        set.add("3");
        set.add("4");


        for (String s : set) {
            System.out.println(s);
        }
    }
}

输出如下

 try语句的升级

传统的异常捕获语句如下所示,以流为例

public static void main(String[] args) {
    InputStreamReader reader = null;


    try {
        reader = new InputStreamReader(System.in);


        reader.read();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

jdk8中可以在try后面跟一对()内进行变量的实例化,然后就不用写finally了,JVM会自行释放资源

public static void main(String[] args) {
    try (InputStreamReader reader = new InputStreamReader(System.in)) {
        reader.read();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

jdk9中,()内的变量可以在外面实例化了,相关资源还是由JVM释放,但此时()内的变量自行成为final变量,不可再修改

public static void main(String[] args) {
    InputStreamReader reader = new InputStreamReader(System.in);
    
    try (reader) {


        reader.read();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

如果有多个要捕获异常的资源,那么在()里用;隔开即可

public static void main(String[] args) {
    InputStreamReader reader = new InputStreamReader(System.in);
    InputStreamReader reader0 = new InputStreamReader(System.in);


    try (reader; reader0) {


        reader.read();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

下划线命名标识符的限制

jdk9中不能使用_做为标识符名了

public static void main(String[] args) {
    String _ = "szc"; // 编译报错
}

String存储结构的变化

jdk8及以前,String使用char[]存放字符串

jdk9中就换成了字节数组

这么做的原因,是因为char数组使用两个字节存放一个字符,而占用堆空间的绝大多数对象都是String对象,绝大多数String存储的是拉丁字符,这些拉丁字符只使用1个字节就足够存放,所以jdk9中String对象使用了byte数组存放字符串。

jdk9把utf-16字符数组换成了字节数组,外加一个编码域,用来表示此字符串应该用什么编码规则编解码。如果编码域指定的编码是latin-1/ISO-8859-1,那么就是用一个字节表示一个字符;如果指定的编码是utf-16,就用两个字节表示一个字符

StringBuilder和StringBuffer的父类AbstractStringBuilder也应用了这一变化,使用字节数组存储字符串

快速创建只读集合

过去创建只读集合,需要调用Collections中的unmodifiableCollection方法

List<String> list = new ArrayList<>();

list.add("1");
list.add("2");
list.add("3");

Collection<String> strings = Collections.unmodifiableCollection(list);

上面的strings对象就是只读的,不能修改或添加数据

现在,可以直接调用具体的unmodifiable+集合名方法来创建具体的只读集合

List<Integer> integers = Collections.unmodifiableList(Arrays.asList(1, 2, 3));

下面是创建只读映射的方法

Map<Object, Object> map = Collections.unmodifiableMap(new HashMap<>() {
    { // 插入代码块,插入键值对
        put("a", 1);
        put("b", 2);
        put("c", 3);
    }
});


map.forEach((k, v) -> System.out.println(k + "-" + v));

上面的代码可以简化成of方法的调用

List<Integer> integers = List.of(1, 2, 3);


Map<Object, Object> map = Map.of("a", 1, "b", 2, "c", 3); // 入参顺序:k1, v1, k2, v2...


Map<String, Integer> map1 = Map.ofEntries(Map.entry("a", 1), Map.entry("b", 2), Map.entry("c", 3)); // 传入一个个键值对entry对象


map.forEach((k, v) -> System.out.println(k + "-" + v));

其中的integers和map对象就都是只读的

增强的流api

takeWhile

读取,直到遇见不符合条件的元素

List<Integer> list = Arrays.asList(11, 32, 44, 29, 52, 72, 82, 64, 4, 19);
list.stream().takeWhile(integer -> integer < 50).forEach(System.out::println);

输出如下,不会输出后面的4和19

dropWhile

作用和takeWhile互补,一直不读取,直到遇见不符合条件的元素

List<Integer> list = Arrays.asList(11, 32, 44, 29, 52, 72, 82, 64, 4, 19);
list.stream().dropWhile(x -> x < 50).forEach(System.out::println);

输出如下

of()方法和ofNullable()方法

of()可以创建含有一个null对象的流

Stream.of(1, 2, 4, null).forEach(System.out::println);

输出如下

ofNullable()可以创建只有一个null对象的流

Stream.ofNullable(null).forEach(System.out::println);

什么也不会输出,ofNullable()方法也可以传入非空对象

System.out.println(".................");
Stream.ofNullable(null).forEach(System.out::println);
System.out.println(".................");
Stream.ofNullable(2).forEach(System.out::println);

输出如下

iterator()重载方法

通过iterate()的第二个参数控制迭代进行的条件

Stream.iterate(0, x -> x < 5, x -> x + 1).forEach(System.out::println);

以上代码类似于

for (int i = 0; i < 5; i++) {
    System.out.println(i);
}

输出结果

Optional类中的stream()方法

Optional.ofNullable(list).stream().forEach(System.out::println);

输出如下

如果要对Optional类的stream对象进行铺平的话,可以调用flatMap()方法

Optional.ofNullable(list).stream().flatMap(x -> x.stream()).forEach(System.out::println);

输出如下

多分辨率图像api

在mac上,jdk已经支持视网膜显示,但jdk8及以前并不支持windows和linux上的多分辨率显示

jdk9使用了比现在更先进的api:Direct2D for Windows和GTK+,而不是Xlib for Linux,支持图形、窗口和文本的自动缩放;而且提供了处理多分辨率图像的能力,适配不同的DPI。

全新的http客户端api

2015年的http2允许服务器主动push(推送)数据,也就是可以发送比客户端请求更多的数据

使用示例

public class HttpClient2Test {
    public static void main(String[] args) throws IOException, InterruptedException {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com"))
                .GET().build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());

        System.out.println(response.statusCode());
        System.out.println(response.version().name());
        System.out.println(response.body());
    }
}

在module-info.java中引用httpclient

requires jdk.incubator.httpclient;

被废弃的api

主要是Applet和appletviewer的api都被标记为废弃,因为主流浏览器已经取消了对java浏览器插件的支持,H5的出现更是加速了java浏览器插件的灭亡,我们可以使用java Web Start来代替applet,以实现从浏览器启动或安装应用程序

智能java编译工具sjavac

sjavac第一个阶段用于提升多核处理器下的编译速度,第二阶段使其成为默认的编译工具,并且能够编译运行低版本的java代码

统一的jvm日志系统

对所有jvm组件引入一个单一的日志系统,从而让这些组件支持细粒度的和易配置的jvm日志,避免碎片化。

javadoc的H5支持

jdk9中javadoc的输出,符合兼容H5标准,主要多了一个search功能

js的Nashorn引擎升级

Nashorn让java中嵌入js,并提供js解释引擎。jdk9中包含了一个用来解析Nashorn的ECMAScript语法树的api,这使得IDE和服务端不需要Nashorn的内部类,就能解析ECMAScrpit代码

动态编译器

引入AOT(Ahead Of  Time),这个功能使得java应用在JVM启动前,就被编译成二进制代码。此功能在jdk9中处于实验阶段,稳定版要到jdk10中发布

总结

jdk9中还看不到的功能:

1)、标准化的和轻量级的json api

2)、新的货币api

展望:

1)、传统的大型企业或互联网应用,正在被云端、容器化应用、模块化微服务甚至FaaS所替代

2)、java标榜面向对象,现在却毫不顾忌地加入面向接口编程思想,又加入匿名对象的概念,这都是对传统面向对象的冲击

3)、java需要在新的计算场景下,改进开发效率

jdk11新特性

局部变量自动类型推断

public class Main {
    public static void main(String[] args) {
        var s = "s";
        System.out.println(s.getClass().getCanonicalName()); // java.lang.String
    }
}

自动类型推断必须使用var,类型推断后,类型就不能变了

//        s = 1; // 不可再赋予新的类型
        s = "a"; // ok

var不是一个关键字

        int var = 1;

var变量必须赋初值

//        var b; // 必须赋初值

类属性不能用var

public class Main {
//    private var x = 1; // 类成员变量不能使用var
}

可用于lambda表达式

List.of(1, 2, 4, 5, 3).stream().map((var x) -> x + "_").forEach(System.out::println);

以及函数式接口

Consumer<String> consumer = (var t) -> System.out.println(t.toUpperCase());
consumer.accept("ss"); // SS

也可用于给参数加注解

Consumer<String> consumer = (@Deprecated var t) -> System.out.println(t.toUpperCase());

以下方式就不对了

// Consumer<String> consumer = (@Deprecated t) -> System.out.println(t.toUpperCase()); // 注解后面不能直接跟变量名

String类新增方法

判断是否是空白字符串

String s1 = "\t \n";
System.out.println(s1.isBlank()); // 判断是否为空白 true

去除首尾空白

s1 = "\t sss\n";
System.out.println(s1.trim().length()); // 去除首尾空白,可以去除全角空格
System.out.println(s1.strip().length()); // 去除首尾空白,但不能去除全角空格
System.out.println(s1.stripLeading()); // 去除头部空白
System.out.println(s1.stripTrailing()); // 去除尾部空白

以上代码输出如下

4
3
sss

     sss

lines()方法可以对字符串每一行进行流式处理

"asc\nccc\nwww".lines().map(str -> str.toUpperCase()).forEach(System.out::println);

输出如下

ASC
CCC
WWW

repeat()方法重复此字符串

System.out.println("a".repeat(5)); // aaaaa

Optional方法增强

增加了对空值的处理

System.out.println(Optional.ofNullable(null).orElse("b")); // 如果为空,返回"b"
System.out.println(Optional.ofNullable(null).orElseGet(() -> "b")); // 也可以使用函数式接口实现orElse()
System.out.println(Optional.ofNullable(null).orElseThrow()); // 如果为空,排除异常

以上代码输出如下

b
b
Exception in thread "main" java.util.NoSuchElementException: No value present
    at java.base/java.util.Optional.orElseThrow(Optional.java:382)
    at com.szc.Main.main(Main.java:42)

transferTo()方法

文件输出可以直接调用inputStream的transferTo()方法,将输入流中数据直接复制刷写到输出流中

try (var inputStream = new FileInputStream("D:/test.jar");
     var outputStream = new FileOutputStream("test2.jar")) {
    inputStream.transferTo(outputStream);
} catch (Exception e) {
    e.printStackTrace();
}

移除和废弃的一些内容

移除的:com.sun.awt.AWTUtilities、sum.misc.Unsafe.defineClass(被java.lang.invoke.MethodHandles.Lookup.defineClass替代)、Thread.destroy()、Thread.stop(Throwable)、sun.nio.disableSystemWideOverlappingFileLockCheck属性、sun.locale.formatasdefault属性、jdk.snmp模块、javafx模块、javaMissionControl等

JavaEE和CORBA模块,比如java.xml部分、java.corba、java.transaction、java.activation等模块,但是新增了java.transaction.xa模块

废弃的:-XX:+AggressiveOpts、-XX:UnlockCommercialFeatures、-XX:+LogCommercialFeatures

Nashorn js引擎,可以考虑使用GraalVM

pack200和unpack200,这是以前压缩jar包的工具,现在被废弃了

更简化的编译运行程序

如果java文件里没有使用别的文件里的自定义类,那么就可以直接使用java就可以编译运行java文件,也不会输出class文件

如果有两个main方法,就只会执行第一个

class A
{
    public static void main(String[] args) {
        System.out.println("aaa");
    }
}


public class Test
{
    public static void main(String[] args) {
        System.out.println("bbb");
    }
}

输出如下

Unicode10

Unicode10加入了8518个字符,4个脚本和56个新的emoji表情符号

新增的Epsilon收集器

这是一个处理内存分配但不负责内存回收的GC,堆内存用完,JVM即刻退出。关于垃圾回收和垃圾回收器,以及GC参数设置,请参见文章JVM学习之垃圾回收和垃圾回收器,文章最后也提到了EpsilonGC、ZGC、雪兰多GC等新的垃圾回收器。

如果启用了Epsilon收集器,System.gc()的调用就没有了意义。

启用Epsilon方法:-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

测试代码如下,配置参数:-Xms10m -Xmx10m -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

package com.szc;

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Test> tests = new ArrayList<>();
        int count = 0;
        while (true) {
            tests.add(new Test());
            count++;
            if (count == 500) {
                tests.clear();
            }
        }
    }
}


class Test {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize..");
    }
}

输出如下

D:\develop\jdk\jdk-11.0.7\bin\java.exe -Xms10m -Xmx10m -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC "-javaagent:D:\develop\IntelliJ IDEA Community Edition 2019.1.3\lib\idea_rt.jar=52381:D:\develop\IntelliJ IDEA Community Edition 2019.1.3\bin" -Dfile.encoding=UTF-8 -classpath D:\develop\ideaWorkspace\JDK11Test\target\classes com.szc.Main

Terminating due to java.lang.OutOfMemoryError: Java heap space

Process finished with exit code 3

而去掉EpsilonGC之后的输出如下所示

 可见尽管tests.clear()调用之后原有的500个对象成为了垃圾对象,但堆爆满时依旧没有发生GC去回收任何对象

EpsilonGC主要用途如下:

性能测试(过滤GC引起的性能消耗,相当于控制变量)、内存压力测试(看看不回收的情况下,到底能不能消耗指定大小的内存)、执行非常短的任务(GC反而是浪费时间)、VM接口测试、延迟吞吐量的改进等实验性质的调优

ZGC

低延迟的ZGC,不管堆内存多大,都能使得STW时间不会超过10ms;和现有的G1相比,应用吞吐能力的下降不会超过15%

ZGC是一个并发、基于区域(region)、标记压缩算法的GC,只有根结点扫描阶段会发生STW,因此停顿时间不会随着堆内存的增长和存活对象的增长而增长

启用ZGC的方法:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

目前ZGC只能用于64位的linux操作系统下

完全支持linux容器(包括docker)

jdk11以前的java应用程序在docker中运行的性能会下降,但现在此问题在容器控制组(cgroups)的帮助下得以解决,使JVM和docker配合得更加默契

G1的完全并行GC

jdk11升级了G1,使我们可以免费享受彻底并行的Full GC,快速的卡表扫描

免费的低耗能分析(Low-Overhead Heap Profiling)

通过JVMTI的SampledObjectAlloc回调提供一个低开销的堆分析方式

新的加密算法

用ChaCha20-Poly1305这种更加高效安全的加密算法,代替RC4;采用新的默认根权限证书集,跟随最新的HTTPS安全协议TLS1.3

Flight Recorder

用来记录java程序的运行,现在从商业版中开源出来了。

有两种使用方式:程序开始前,添加-XX:StartFilghtRecording参数;也可以在程序启动后,通过jcmd命令启动jfr

D:\develop\jdk\jdk-11.0.7\bin>jcmd 20412 JFR.start
20412:
Started recording 1. No limit specified, using maxsize=250MB as default.


Use jcmd 20412 JFR.dump name=1 filename=FILEPATH to copy recording data to file.


D:\develop\jdk\jdk-11.0.7\bin>jcmd 20412 JFR.dump name=1 filename=out.jfr
20412:
Dumped recording "1", 359.5 kB written to:


C:\Users\songzeceng\AppData\Local\JetBrains\IdeaIC2020.1\compile-server\out.jfr


D:\develop\jdk\jdk-11.0.7\bin>jcmd 20412 JFR.stop name=1
20412:
Stopped recording "1".

其中的20412是待测的java程序进程号,输出的文件路径可知是C:\Users\songzeceng\AppData\Local\JetBrains\IdeaIC2020.1\compile-server\out.jfr,但这是二进制文件,基本看不了

在jdk12及以后,才有了jfr命令可以查看jfr文件,例如查看CPU加载信息

D:\develop\jdk\jdk-12\bin>jfr print --events  CPULoad C:\Users\songzeceng\AppData\Local\JetBrains\IdeaIC2020.1\compile-server\out.jfr
jdk.CPULoad {
  startTime = 00:55:26.793
  jvmUser = 6.37%
  jvmSystem = 0.70%
  machineTotal = 10.31%
}


jdk.CPULoad {
  startTime = 00:55:27.798
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 4.57%
}


jdk.CPULoad {
  startTime = 00:55:28.796
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 4.91%
}


jdk.CPULoad {
  startTime = 00:55:29.798
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 4.05%
}


jdk.CPULoad {
  startTime = 00:55:30.803
  jvmUser = 0.13%
  jvmSystem = 0.13%
  machineTotal = 2.99%
}


jdk.CPULoad {
  startTime = 00:55:31.804
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.36%
}


jdk.CPULoad {
  startTime = 00:55:32.804
  jvmUser = 0.13%
  jvmSystem = 0.13%
  machineTotal = 6.71%
}


jdk.CPULoad {
  startTime = 00:55:33.807
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.66%
}


jdk.CPULoad {
  startTime = 00:55:34.814
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.82%
}


jdk.CPULoad {
  startTime = 00:55:35.829
  jvmUser = 0.00%
  jvmSystem = 0.39%
  machineTotal = 5.01%
}


jdk.CPULoad {
  startTime = 00:55:36.821
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.54%
}


jdk.CPULoad {
  startTime = 00:55:37.822
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.29%
}


jdk.CPULoad {
  startTime = 00:55:38.841
  jvmUser = 0.00%
  jvmSystem = 0.38%
  machineTotal = 4.26%
}


jdk.CPULoad {
  startTime = 00:55:39.824
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 3.85%
}


jdk.CPULoad {
  startTime = 00:55:40.832
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 8.03%
}


jdk.CPULoad {
  startTime = 00:55:41.835
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.34%
}


jdk.CPULoad {
  startTime = 00:55:42.854
  jvmUser = 0.00%
  jvmSystem = 0.38%
  machineTotal = 5.35%
}


jdk.CPULoad {
  startTime = 00:55:43.839
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 3.89%
}


jdk.CPULoad {
  startTime = 00:55:44.843
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 3.99%
}


jdk.CPULoad {
  startTime = 00:55:45.845
  jvmUser = 0.00%
  jvmSystem = 0.39%
  machineTotal = 5.01%
}


jdk.CPULoad {
  startTime = 00:55:46.846
  jvmUser = 0.13%
  jvmSystem = 0.00%
  machineTotal = 6.59%
}


jdk.CPULoad {
  startTime = 00:55:47.844
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 2.83%
}


jdk.CPULoad {
  startTime = 00:55:48.843
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 3.45%
}


jdk.CPULoad {
  startTime = 00:55:49.845
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 3.61%
}


jdk.CPULoad {
  startTime = 00:55:50.848
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 3.40%
}


jdk.CPULoad {
  startTime = 00:55:51.852
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 3.73%
}


jdk.CPULoad {
  startTime = 00:55:52.869
  jvmUser = 0.00%
  jvmSystem = 0.38%
  machineTotal = 4.37%
}


jdk.CPULoad {
  startTime = 00:55:53.855
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 5.43%
}


jdk.CPULoad {
  startTime = 00:55:54.859
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 7.28%
}


jdk.CPULoad {
  startTime = 00:55:55.858
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 7.23%
}


jdk.CPULoad {
  startTime = 00:55:56.885
  jvmUser = 0.00%
  jvmSystem = 0.38%
  machineTotal = 9.12%
}


jdk.CPULoad {
  startTime = 00:55:57.865
  jvmUser = 0.00%
  jvmSystem = 0.27%
  machineTotal = 3.01%
}


jdk.CPULoad {
  startTime = 00:55:58.865
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.01%
}


jdk.CPULoad {
  startTime = 00:55:59.885
  jvmUser = 0.00%
  jvmSystem = 0.38%
  machineTotal = 4.91%
}


jdk.CPULoad {
  startTime = 00:56:00.875
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 10.92%
}


jdk.CPULoad {
  startTime = 00:56:01.872
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 15.21%
}


jdk.CPULoad {
  startTime = 00:56:02.874
  jvmUser = 0.13%
  jvmSystem = 0.00%
  machineTotal = 5.43%
}


jdk.CPULoad {
  startTime = 00:56:03.875
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 5.02%
}


jdk.CPULoad {
  startTime = 00:56:04.875
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 8.98%
}


jdk.CPULoad {
  startTime = 00:56:05.880
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 3.93%
}


jdk.CPULoad {
  startTime = 00:56:06.881
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.36%
}


jdk.CPULoad {
  startTime = 00:56:07.882
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 2.74%
}


jdk.CPULoad {
  startTime = 00:56:08.883
  jvmUser = 0.13%
  jvmSystem = 0.13%
  machineTotal = 4.98%
}


jdk.CPULoad {
  startTime = 00:56:09.887
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.79%
}


jdk.CPULoad {
  startTime = 00:56:10.893
  jvmUser = 0.00%
  jvmSystem = 0.13%
  machineTotal = 4.32%
}


jdk.CPULoad {
  startTime = 00:56:11.897
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.94%
}


jdk.CPULoad {
  startTime = 00:56:12.898
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.00%
}


jdk.CPULoad {
  startTime = 00:56:13.900
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.47%
}


jdk.CPULoad {
  startTime = 00:56:14.910
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 3.98%
}


jdk.CPULoad {
  startTime = 00:56:15.903
  jvmUser = 0.00%
  jvmSystem = 0.26%
  machineTotal = 4.30%
}




D:\develop\jdk\jdk-12\bin>

jdk12新特性

新的Shenandoah(雪兰多)收集器

雪兰多收集器使用的内存结构和G1类似,都是将内存划分为区域,整体流程也和G1相似。关于G1,请参见文章JVM学习之垃圾回收和垃圾回收器相关章节。

最大的区别在于雪兰多收集器实现了并发疏散环节,引入的Brooks Forwarding Pointer技术使得GC在移动对象时,对象的引用仍然可以访问,这样就降低了延迟,相关团队宣城是使99.9%的暂停小于10ms。

其工作周期如下:

1)、初始标记,并启动并发标记阶段

2)、并发标记遍历堆阶段

3)、并发标记完成阶段

4)、并发整理回收无活动区域阶段

5)、并发疏散,整理内存区域

6)、初始化更新引用阶段

7)、并发更新引用

8)、完成引用更新阶段

9)、并发回收无引用区域阶段

启用方法:

XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

微基准表达式

JMH(Java Microbenchmark Harness)是专门用于代码微基准测试的工具套件,用于方法层面的测试,精度可以达到微秒级。

其主要应用场景有:

1)、想准确知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性

2)、对比接口的不同实现在给定条件下的吞吐量

3)、查看多少百分比的请求在多长时间内完成

jdk12添加了一套新的微基准测试,简化了现有微基准测试的运行和新微基准测试的创建过程

switch表达式

这个版本是jdk13的预览功能,所以要将jdk、jre和目标字节码的版本设置为13

首先是箭头操作符,可以同时处理多个case

public class Main {
    public static void main(String[] args) {
        Fruit f = Fruit.APPLE;


        switch (f) {
            case APPLE -> System.out.println(1);
            case ORANGE, GRAPE -> System.out.println(2);
            case PEAR, MANGO, WATERMALLON -> System.out.println(3);
            default ->
                System.out.println("No such fruit");
        }
    }
}


enum Fruit {
    APPLE, ORANGE, GRAPE, PEAR, MANGO, WATERMALLON
}

输出自然是1

然后也可以把switch块当成一个表达式,用变量接收它的值

public class Main {
    public static void main(String[] args) {
        Fruit f = Fruit.APPLE;


        int ret = switch (f) {
            case APPLE -> 1;
            case ORANGE, GRAPE -> 2;
            case PEAR, MANGO, WATERMALLON -> 3;
            default ->
                throw new IllegalStateException("error");
        };
        System.out.println(ret);
    }
}


enum Fruit {
    APPLE, ORANGE, GRAPE, PEAR, MANGO, WATERMALLON
}

这个改进类似于kotlin中的when表达式

JVM常量api

java.base模块新增了java.lang.constant包,里面定义了一系列的基于值的符号引用类型,用于描述每种可加载常量,主要用于节省内存

目前基本数据类型的包装类和String都实现了java.lang.constant.Constable和java.lang.constant.ConstantDesc两个接口,两个接口定义如下

package java.lang.constant;


import java.lang.Enum.EnumDesc;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle.VarHandleDesc;

public interface ConstantDesc {
    Object resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException;
}

///

package java.lang.constant;


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.Optional;
public interface Constable {
    Optional<? extends ConstantDesc> describeConstable();
}

主要作用就是让加载这些类的时候更加快捷

只保留一个AArch64实现

之前的jdk存在两个64位的ARM端口,两个端口都产生了aarch64实现,但把src/hotspot/cpu/arm中的称为arm64,把open/src/hotspot/cpu/aarch64称为aarch64

jdk12中删除了arm64,只保留其中关于32位的arm端口实现,64位的全交给open/src/hotspot/cpu/aarch64中的AArch64来实现

这样就让开发贡献者避免实现两个64的arm

默认类数据共享归档文件

在同一台机器上启动多个JVM时,如果每个JVM都单独装在自己的类,那么启动成本和内存占用就很高。所以java团队就引入了类数据共享机制(Class Data Sharing,简称CDS),即一些核心类在JVM进程间共享,每个JVM只需要装载自己的应用类即可

CDS目前可以支持系统类、引导类和应用类,jdk12对此的改进是将其默认使能:-Xshare:auto,并对64位平台的jdk架构进行优化,自动调用java -Xshare:dump,生成的文件保存在JavaHome/lib/server目录下,名为classes.jar

可中止的G1混合收集器

当G1回收耗时超过目标暂停时间,就会终止垃圾回收过程

主要思路是:把回收集分为必须部分和可选部分,优先处理必须部分。

必须部分主要包括G1不能递增处理的部分(如年轻代),也可以包含老年代以提高效率。

在优先处理必须部分时,会维护可选部分的一些数据,但产生的CPU开销不会超过1%,而且会增加本机内存使用率;处理完必须部分后,如果还有时间,就处理可选部分,如果剩下时间不够,就可能只处理可选部分的一个子集。处理完一个子集后,G1会根据剩余时间来决定是否继续收集。

G1及时返回未使用的已分配内存

jdk12中的G1将在应用程序不活动期间定期生成或持续循环检测整体的java堆使用情况,以便更及时地将java堆中不使用的内存返回给OS。这一改进带来的优势在云平台的容器环境中更加明显,此时内存利用率的提高会直接降低经济成本

jdk12为此新增了两个参数:G1PeriodicGCInterval和G1PeriodicGCSystemLoadThreshold,两者均为0的话表示禁用此功能。前者表示定期检测的周期,后者表示触发内存返回的系统负载阈值

另有一个参数G1PeriodicInvokesConcurrent来控制定期GC的类型,默认是Full GC,如果设置值了,就会继续上一个或启动一个新的并发周期

如果定期GC严重影响程序执行,就要考虑程序的CPU负载,或者让用户禁用定期GC。

String中新增的方法

transform()方法

对字符串进行链式转换

String transform = "szc".transform(x -> x + "szc").transform(String::toUpperCase);
System.out.println(transform); // SZCSZC

indent()方法

在字符串的每一行前面加空格

String indent = "szcc\ncc\nanyang".indent(3);
System.out.println(indent);

输出如下,每一行前面加了3个空格

   szcc
   cc
   anyang

Files中新增的方法——mismatch()

返回两个Path对应的文件内容中首次字节不匹配发生的行索引,从0开始。如果返回-1,就是指两个文件内容完全一样

try (FileWriter fileWriter1 = new FileWriter("test1.txt");
     FileWriter fileWriter2 = new FileWriter("test2.txt")) {
    fileWriter1.write("a");
    fileWriter1.write("b");
    fileWriter1.write("c");


    fileWriter1.close();


    fileWriter2.write("a");
    fileWriter2.write("B");
    fileWriter2.write("C");


    fileWriter2.close();


    System.out.println(Files.mismatch(Path.of("test1.txt"), Path.of("test2.txt")));
} catch (IOException e) {
    e.printStackTrace();
}

输出为1

其他新增项

新增支持unicode11,包括684个字符、11个块和7个脚本

支持压缩数字格式化,比如1000可以格式化为1K,1000000可以格式化为1M,也可以根据指定的语言环境格式化

var cnf = NumberFormat.getCompactNumberInstance(Locale.CHINA, NumberFormat.Style.SHORT);
System.out.println(cnf.format(1L << 30));
System.out.println(cnf.format(1000));
System.out.println(cnf.format(1L << 40));
System.out.println(cnf.format(1_92000));
System.out.println(cnf.format(192000));

输出如下

11亿
1,000
1兆
19万
19万

jdk13新特性

动态CDS档案

CDS是jdk12的特性,jdk13中这个特性支持java应用执行之后进行动态归档,以后执行java程序后一些类就可以直接从这些归档文件中加载了

ZGC:提交未使用的堆内存

ZGC在jdk11中引入的收集器,jdk13中使能了向OS提交未使用的堆内存

ZGC中的区域称之为ZPage,当ZGC压缩堆时,ZPage被释放,然后变成ZPageCache,最后使用LRU算法对PageCache区域进行定时清除。时间间隔默认为5分钟,用户自定义时间间隔尚未实现,而如果-Xms和-Xmx相等的话,这个功能就相当于没有

重新实现旧版套接字api

重新实现了Socket接口,新的实现类是NioSocketImpl来替换jdk1.0的PlainSocketImpl,其有以下特点:

1)、使用和NIO相同的内部结构,无需再使用本地代码

2)、和现有的缓冲区机制集成在一起,因此不需要为IO使用线程栈

3)、使用java.util.concurrent锁,而不是synchronized同步方法,增强了并发能力

4)、新的实现是jdk13中的默认实现,但旧的实现还没有删除,可以通过系统属性jdk.net.usePlainSocketImpl(不为空,且不为false)来切换到旧版本

switch表达式中引入yield

default语句里可以单独用yield返回值

String x = "3";
int i = switch (x) {
    case "1" -> 10;
    case "3" -> 30;
    case "4", "5" -> 20;
    default -> {
        yield x.length();
    }
};

case语句里也可以使用yield,但yield和->只能存在一种

i = switch (x) {
    case "1":
        System.out.println("aa");
        yield 10;
    case "3":
        System.out.println("bb");
        yield 30;
    case "4", "5":
        yield 20;
    default:
        yield x.length();
};

yield用于从switch语句里返回值,作用域要比return小,和->相比它又允许case里有更多的逻辑

文本块

文本块用来表示多行字符串,用一对"""包住即可

String s = """
        <html>
            <head>
                <meta charset="utf-8"/>
            </head>
            <body>
                <p>aaa</p>
            </body>
        </html>
        """;

比如表示SQL时

s = """
        select * from students
        where id in (12, 13, 14, 15)
        order by grade desc
        """;

文本块避免了换行转义,提高了可读性

文本块第一个字符为行终止符,所以第一个可见字符必须在第二行

s = """
        a
        """;

每一行末尾实际上都有一个行终止符(换行符)。而且,运行时,文本块将被实例化为String实例,所以使用和传统的字符串对象是一样的;编译时,文本块中每一行后面多余的空格会被去掉,前面多余空格数将取决于结束"""的情况:如果结束"""是单独一行的话,保留的空格数将与结束"""对齐;否则全部删除

s = """
        a
        """;
System.out.println(s.length()); // 2
var s1 = """
        a""";
System.out.println(s1.length()); // 1
System.out.println(s1.equals(s)); // false
System.out.println(s1 == "a"); // true

s = """
           a
        """;
System.out.println(s.length()); // 5

文本块可以表示空字符串

s1 = """
        """;
System.out.println(s1.equals("")); // true

文本块中显式\n依旧有效

System.out.println("""
        a\n
        """.length()); // 3

文本块中的"就不用转义了,但"""需要转义

System.out.println("""
        "I`ve got to go, \""" now \""". ", said Jason.
        "Ok, as you wish.", said Mike
        """);

/*
"I`ve got to go, """ now """. ", said Jason.
"Ok, as you wish.", said Mike
*/

文本块也可以拼接,注意结束"""就不要单独一行了

System.out.println("""
        a""" + """
        bcd"""); // abcd

可以使用replace()方法简化拼接过程

String type = "String";
System.out.println("""
        The type is $type""".replace("$type", type)); // The type is String

或者formatted()方法,此方法在jdk13刚被引进,就被标记为移除

String type = "String";
System.out.println("The type is %s".formatted(type)); // The type is String

似乎更推荐String.format()方法

System.out.println(String.format("The type is %s", type)); // The type is String

新增项

1)、添加new FileSystem(Path)、new FileSystem(Path, Map<String, ?>)方法

2)、新的ByteBuffer Bulk get/put方法

3)、支持Unicode12.1

4)、添加-XX:SoftMaxHeapSize,仅对ZGC有用

5)、ZGC最大的堆大小升到16TB

总结

语法层面:改进版的switch表达式、文本块

API层面:NioSocketImpl来替换原有的PlainSocketImpl

GC层面:改进了ZGC,以支持回收未使用的内存

jdk14新特性

instanceof省去了强制类型转换的过程

以前是这么写的

Object obj = "szc";
if (obj instanceof String) {
    String s = (String) obj;
}

现在可以这么写

if (obj instanceof String s) {
    System.out.println(s);
}

类的equals()方法因此也可以被简化

class Monitor {
    private String mName;
    private int mCount;


    @Override
    public boolean equals(Object o) {
        return o instanceof Monitor other && other.mName.equals(mName) && other.mCount == mCount;
    }
}

更详细的空指针异常

在运行时加上参数-XX:+ShowCodeDetailsInExceptionMessages,可以更详细地显示空指针异常。例如以下代码

public class Main {
    public static void main(String[] args) {
        MonitorGroup group = new MonitorGroup();
        group.show();
    }
}


class MonitorGroup {
    private List<Monitor> mMonitors;


    public MonitorGroup() {
    }


    public MonitorGroup(List<Monitor> mMonitors) {
        this.mMonitors = mMonitors;
    }


    public void show() {
        System.out.println(mMonitors.size());
    }
}


class Monitor {
}

运行后报错如下

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "this.mMonitors" is null
    at com.szc.MonitorGroup.show(Main.java:33)
    at com.szc.Main.main(Main.java:18)

报出了执行哪个方法时出现了异常,并且给出了哪个变量为空

Record数据结构

Record数据结构可以更方便地创建java bean类

1)、首先创建时选用record

2)、然后给出属性即可,这些属性都是final常量,也就是只能被赋值一次

public record User(String mName, int mAge) {
}

以上代码就相当于原始的属性+有参构造方法+get方法+toString()+equals()+hashcode()方法

User u = new User("szc", 23);
System.out.println(u); // User[mName=szc, mAge=23]
System.out.println(u.mName()); // szc
System.out.println(u.mAge()); // 23
System.out.println(u.equals(new User("szc", 23))); // true
System.out.println(u.hashCode() + ", " + new User("szc", 23).hashCode()); // 3546299, 3546299

record也可以覆写或定义方法,以及静态属性和方法

public record User(String mName, int mAge) {
    public static String sNation;


    public static String getNation() {
        return sNation;
    }


    public void show() {
        System.out.println("Name = " + mName + ", age = " + mAge);
    }
}

但是不能声明非静态的属性,而且record类也不能是抽象类

public record User(String mName, int mAge) {
    public static String sNation;


    // public int mCount;
}

// abstract record Person() { }

而且record类是final的,不能有继承语句

// record Count() extends Thread { }

jdk12/13中的switch表达式正式转正

switch表达式终于不是测试功能了..

增强版的文本块

如果不想换行,可以加上\

System.out.println("""
        aaa\
        bbb
        """); // aaabbb\n

弃用Parallel Scavenge和Serial Old的GC组合

下图中绿色虚线和CMS也被抛弃了,所以只剩下Serial GC -- Serial Old GC、Parallel Scavenge -- Parallel Old和G1三种GC(组合)了。关于这些经典垃圾回收器,请参见文章JVM学习之垃圾回收和垃圾回收器相关章节。

windows和mac上可用ZGC

使用参数为-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

结语

虽然jdk迭代更新很快,但由于成规模的项目用的都是jdk7或jdk8,而且很多第三方库也都是基于jdk7或8的,所以选择对项目的jdk版本升级还是要三思而行,因为这些新的jdk似乎只是让开发更容易,而客户感受不到这些改进,那么带来的经济效益就值得商榷了。

不过,虽然升级jdk或者其他代码重构可能是费力不讨好的事儿,但尝个鲜也无可厚非,就当长见识,开开眼

猜你喜欢

转载自blog.csdn.net/qq_37475168/article/details/107108462