jdk10 -jdk 15特性

231 阅读1小时+

JDK10特性

一、JAVA10概述

  2018年3月21日, Oracle官方宣布JAVA10正式发布

  JAVA9和java10 都不是 LTS (Long-Term-Support)版本.和过去的JAVA大版本升级不同,这两个只有半年左右的开发和维护时间. 而JAVA11 也是就是18.9,才是JAVA之后的第一个长期支持版本

  JAVA10 一共定义了109个新特性,其中包含JEP,对程序员来说,真正的新特性也就一个,还有一些新的API和JVM规范以及JAVA语言规范上的改动.

  JAVA10 的12个JEP (JDK Enhancement Proposal特性加强协议) ,可参阅官方文档openjdk.java.net/projects/jd…

12b24e81712b49ab8c6d78cd42624170.png

具体的新增特性:

286: 局部变量类型推断

296: JDK库合并

304: 统一的垃圾回收接口

307:为G1提供并行的Full GC

310:应用程序类数据共享

312: ThreadLocal握手交互

313: 移除JDK中附带的javah工具

314: 使用附加的Unicode语言标记拓展

316:能将对内存占用分配给用户指定的备用内存设备

317:使用基于JAVA的JIT编译器

319: 根证书

322:基于时间的发布版本

二、语法层次的变化

1. 局部变量的类型推断

  局部变量的显示类型声明,常常认为是不必须的,给一个好听的名字经常可以很清楚的表达出下面应该怎样继续.减少了啰嗦和形式的代码,避免了信息的冗余,而且对齐了变量名,容易阅读。

  作为JAVA程序员,在声明一个变量时,我们一般都是书写两次变量类型,第一次用于声明变量类型,第二次用于构造器

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

  变量的声明类型书写复杂且较长,尤其是加上泛型的使用

Iterator<Map.Entry<Integer,Student>> iterator=set.iterator();

  我们也经常声明一种变量,它只被使用一次,并且是在下一行代码中:

URL url=new URL("http://www.boge.com");
URLConnection connection= url.openConnection();
Reader reader =new BufferedReader(new InputStreamReader(connection.getInputStream));

总之: 变量标识符(变量名)就可以让我清楚的知道变量如何使用,没必要前面加上一大串的类型声明

  局部变量推断有点类似JavaScript中的弱变量类型的写法,通过后面的数据来推断前面的数据类型,数据类型的声明统一为var.

image-20240322091220800.png

2. 不能使用类型推断的场景

  在有些情况下我们是不能使用类型推断的。具体有如下的场景

2.1 变量的声明

变量的声明不能使用类型推断,因为无法推断

// 根据右边的数据推断类型,不赋值压根没给推断的机会,这是错的
var userName;

2106c9f4574a4f1086b913935938c22d.png

2.2 初始值null

  初始值为null的情况也是不行的。因为同样没有办法推断

// null值无法推断数据类型,这是不能使用类型推断
var userName=null;

2106c9f4574a4f1086b913935938c22d.png

2.3 lambda表达式

        // 这个是可以的
        Supplier<Double> supplier=()-> Math.random();
        // 这个是不行的 lambda表达式需要显式数据类型,不然无法明确是哪个接口
        var supplier2=()->Math.random();

2106c9f4574a4f1086b913935938c22d.png

2.4 方法引用

        // 这是可以的进行方法引用的
        Consumer<String> consumer=System.out::println;
        // 这是不可以的,无法明确接口类型
        var consumer2=System.out::println;

2106c9f4574a4f1086b913935938c22d.png

2.5 为数组静态初始化

        //动态初始化数据可以进行类型推断
        var arr =new String[10];
        //静态初始化数组不可以使用类型推断
        var arr2 = {"a12","222","333","444","555"};

2106c9f4574a4f1086b913935938c22d.png

2.6 成员变量不能使用

类型推断仅仅是局部变量,成员变量不可以使用类型推断

public class Demo07 {
    // 成员变量不能使用类型推断
    var name="小明";
    public void testMethod1(){
        // 局部变量可以使用类型推断
        var localname="小明";
    }
}

2106c9f4574a4f1086b913935938c22d.png

2.7 其他不可以的场景

// 情况1 没有初始化的局部变量声明
var s; var x=null;
// 情况2 方法的返回值类型
public var test1()
// 情况3 方法的参数类型
public void test2(var a,var b)
// 情况4 构造器的参数类型
public Person(var name,var age)
// 情况5 属性
class Person{
    var name;
}
// 情况6 catch块
try{
}catch(var e){
}

以下两点需要注意

  1. var不是一个关键字

我们无需担心变量名或者方法名会与var发生冲突,因为var实际上不是一个关键字,而是一个类型名,只有在编译器需要知道类型的地方法才会用到它.除此之外,他就是一个普通的合法标识符.也就是说,除了不能用它做类名,其他都可以.但是又有哪个傻瓜非要用var做类名呢?

  1. 这毕竟不是JavaScript

var并不会改变java是一门静态语言的事实,编译器负责推断出类型,并把结果写入字节码,也就是说,数据类型还是在字节码中的,java还是属于强类型的编程语言,开发人员没有明确写出来而已.而JavaScript是弱类型解释型的脚本语言,和这里的类型推断是两回事.

三、API层次的变化

1. 集合的copyOf方法

 在JDK10中给集合新增了一个copyOf方法。用来创建只读集合

package com.linhao.jdk10;

import com.sun.tools.javac.Main;

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

/**
 * @Author:党刘锋
 * @Package:com.linhao.jdk10
 * @Project:jdk8Demo
 * @name:Demo02
 * @Date:2024/3/22 9:13
 * @Filename:Demo02
 */
public class Demo02 {
    public static void main(String[] args) {
        // JAVA9中新增创建只读的方法
        var strings1 = List.of("Python", "JAVA", "Golang");
        // JAVA10中新增的创建只读集合的方法
        var strings2 = List.copyOf(strings1);
        // 判断两个集合在内存上是否是同一个,结果为true
        System.out.println(strings1==strings2);

        // 创建一个普通集合
        var strings3=new ArrayList<String>();
        // 通过copyOf方法创建一个只读集合
        var strings4 = List.copyOf(strings3);
        // 判断两个集合在内存上是否是同一个,结果为false
        System.out.println(strings3==strings4);
    }
}

结论:copyOf方法的作用通过一个集合返回的是一个只读集合,如果参数本来就是只读集合,那么返回的就是参数,如果参数不是只读集合,就再创造一个只读集合返回。

JDK11特性

一、JAVA11 概述

  2018年9月26日,Oracle官方发布JAVA11.这是JAVA大版本周期变化后的第一个长期支持版本,非常值得关注.最新发布的JAVA11将带来ZGC HttpClient等重要特性,一共17个需要我们关注的JEP,参考文档openjdk.java.net/projects/jd…

具体的特性介绍

  • 181:基于嵌套的访问控制
  • 309:动态类文件常量
  • 315:改进Aarch64 Intrinsics
  • 318:Epsilon:一个无操作的垃圾收集器
  • 320:移除Java EE和CORBA模块
  • 321:HTTP客户端(标准)
  • 323:本地变量语法Lambda参数
  • 324:与Curve25519和Curve448的密钥一致
  • 327: Unicode 10
  • 328:飞行记录器
  • 329: ChaCha20和Poly1305密码算法
  • 330:启动单文件源代码程序
  • 331:低开销堆分析
  • 332: TLS (Transport Layer Security) 1.3
  • 333:ZGC:一个可伸缩的低延迟垃圾收集器 (实验)
  • 335:已弃用Nashorn JavaScript引擎
  • 336:已弃用Pack200工具和API

二、语法层次的变化

1. 局部变量类型推断升级

  局部变量类型推断是java10开始新增的新特性,java11中对局部变量推断进行了升级,在var支持添加注解的语法格式,JAVA10中是无法实现的,在JAVA11中加入了这样的语.

lambda表达式中,注解修饰变量的时候,变量的数据类型必须要写,不能省略,像下面这种写法就是错误的

Consumer<String> con =(@Deprecated  t) -> System.out.println(t.toLowerCase());

这个时候就必须要为小括号中的参数添加数据类型,应该这样写

Consumer<String> con =(@Deprecated String t) -> System.out.println(t.toLowerCase());

java11中,lambda表达式中的参数数据类型可以使用var,但是不能不写

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

三、API层次的提升

1. String新增的方法

  在JDK11中对String处理新增的很多方法。简化对字符串的操作

描述举例
判断字符串是否为空白" ".isBlank(); // true
去除字符串首尾空白" www.boge.com ".strip(); // "www.boge.com"
去除字符串尾部空格" www.boge.com ".stripTrailin(); // " www.boge.com"
去除字符串首部空格" www.boge.com ".stripLeading(); // "www.boge.com "
复制字符串"boge".repeat(2);// "bogeboge"
行数统计"A\nB\nC\nD".lines().count(); // 4
    public static void main(String[] args) {
        // 判断字符串是否为空白
        boolean b = " ".isBlank();// true
        // 去除字符串首尾空白
        String s1 = " www.boge.com ".strip();// "www.boge.com"
        // 去除字符串尾部空格
        String s2 = " www.boge.com ".stripTrailing();// " \n \twww.boge.com"
        // 去除字符串首部空格
        String s3 = " www.boge.com ".stripLeading();// "www.boge.com\n \t "
        // 复制字符串
        String r = "boge".repeat(2);// "bogeboge"
        // 行数统计
        long c = "A\nB\nC\nD".lines().count();// 4
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(r);
        System.out.println(c);
    }

image-20240322093006564.png

2. Optional新增方法

  Optional也增加了几个非常好用的方法,现在可以很方便的把一个Optional转换成一个Stream,或者当一个空Optional时,给它一个替代的. 我们发现从JDK8开始出现Stream以后,每个版本都有相关的更新.

新增方法描述新增版本
boolean isEmpty()判断value是否为空JDK11
T orElseThrow()value非空,返回value,否则抛出NoSuchElementExpceptionJDK10
ifPresentOrElse(Consumer<? super T> action,Runnable emptyAction)value非空,执行参数1功能,如果value为空,执行参数2功能JDK9
Optional<T>or(Supplier<? extends Optional<? extends T> supplier)value非空,返回对应的Optional,value为空,返回形参封装的OptionalJDK9
Stream<T>stream();value非空,返回一个仅包含此value的Steam,否则,返回一个空的StreamJDK9
    public static void main(String[] args) {
        Optional<String> optional =Optional.empty();
        //JDK8 判断value是否存在
        System.out.println(optional.isPresent());
        //JDK11 判断value是否为空
        System.out.println(optional.isEmpty());

        //JDK10 返回value,如果为null则直接抛出 NoSuchElementExpception
        Optional<String> optional2 = Optional.of("element1");
        String value = optional2.orElseThrow();
        System.out.println(value);

        //JDK9  value非空,执行参数1功能,如果value为空,执行参数2功能
        Optional<String> optional3 =Optional.empty();// Optional.of("element1");
        optional.ifPresentOrElse((v)-> System.out.println("value为"+v),()-> System.out.println("value为null"));

        // JDK9 value非空,返回对应的Optional,value为空,返回形参封装的Optional
        Optional<String> optional4 =Optional.empty();// Optional.of("element1");
        Optional<String> optional5 = optional4.or(() -> Optional.of("element2"));
        System.out.println(optional5);

        // JDK9 value非空,返回一个仅包含此value的Steam,否则,返回一个空的Stream
        Optional<String> optional6 =Optional.of("element3");//Optional.empty();
        Stream<String> stream = optional6.stream();
        stream.forEach(System.out::println);
    }

image-20240322093056437.png

3.HTTPClient

  HTTP,用于传输网页的协议,在1997年就被采用1.1的版本中,到2015年,HTTP2才成为标准. HTTP1.1和HTTP2的主要区别就是如何在客户端和服务器之间构建和传输数据, HTTP1.1依赖请求/响应周期. HTTP2允许服务器push数据:它可以发送比客户端请求更多的数据.这使得他可以优先处理并发送对于首先加载网页至关重要的数据.

  JAVA9开始引入一个处理HTTP请求的HTTPClient API,该API支持同步和异步,而在JAVA11中成为正式可用状态,可以在java.net包中找到这个API,它将替代仅适用于bolocking模式的HTTPUrlConnection(创建于Http1.0s时代,并使用了协议无关的方法),并提供对WebSocket和HTTP2的支持

//HttpClient 替换原有的HttpUrlConnection  同步方式
HttpClient client =HttpClient.newHttpClient();
HttpRequest request =HttpRequest.newBuilder(URI.create("http://127.0.0.1:8080/demo")).build();
HttpResponse.BodyHandler<String> respnoseBodyHandler= HttpResponse.BodyHandlers.ofString();
HttpResponse<String> response =client.send(request,respnoseBodyHandler);
String body = response.body();
System.out.println(body);
//HttpClient 替换原有的HttpUrlConnection  异步方式
HttpClient client =HttpClient.newHttpClient();
HttpRequest request =HttpRequest.newBuilder(URI.create("http://127.0.0.1:8080/demo")).build();
HttpResponse.BodyHandler<String> respnoseBodyHandler= HttpResponse.BodyHandlers.ofString();
CompletableFuture<HttpResponse<String>> sendAsync = client.sendAsync(request, respnoseBodyHandler);
sendAsync.thenApply(t-> t.body()).thenAccept(System.out::println);

四、其他变化

1. 更简化的编译运行

  JAVA11 提供了更简化的编译运行程序,编译一个java源代码文件语法应该是

javac Test1.java

  解释执行一个java字节码的语法应该是

java Test1

  在我们目前的知识里面,运行一个java源代码必须经过两个不中,一个是编译,一个是解释执行,而在java11中,通过一个java命令就可以直接搞定了,语法是:

java Test1.java

需要注意的是:

  1. 源代码文件中如果有多个类,执行源文件中的第一个类中主方法,注意这里的第一个是代码顺序的第一个,和是否由public修饰无关
  2. 不可以使用其他源文件中定中自定义的类,当前文件中自定义的类是可以使用的

定义一个源代码文件进行测试

public class HelloJAVA11{
    public static void main(String[] args){
        System.out.println("HelloJAVA11.main");
        // 实例化当前文件中的Person类
        Person p=new Person();
        // 实例化另一个文件中的Student类
        Student stu =new Student();
  
    }
  
}

class Person {
    private String pid;
    private String pname;
  
}
class Test2{
    public static void main(String[] args){
        System.out.println("Test2.main");
    }
}
public class Student{
  
}

如果当前文件中,没有使用其他文件中的类,可以直接运行成功

2106c9f4574a4f1086b913935938c22d.png

如果当前文件中使用类其他文件中的类,那么会出现异常

12b24e81712b49ab8c6d78cd42624170.png

2.ZGC

  GC 是java的主要优势之一(另一个是强大的JVM),永远都是java优化的一个核心点. 然而,当GC的STW(stop the world)太长,就会影响应用的响应时间. 消除或者减少GC的停顿时长,将会使JAVA对更广泛的引用场景成为一个更具有吸引力的平台. 此外,现代系统中可用内存不断增长,用户和程序员希望JVM能够以更高效的方式利用这些内存,并且无需长时间STW. ZGC A Scalable Low-Latency Garbage Collector(Experimental).作为JDK11最瞩目的特征,但是后面带了Experimental,说明是实验版本,也就不建议在生产环境中使用.ZGC是一个并发,基于 region的压缩性垃圾收集器,只有root扫描阶段会STW,因此GC停顿时间不会随着堆的增长和存活对象的增长而变长.

优势:

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

设计目标:

  1. 支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%
  2. 将来还可以扩展实现机制,用以支持很多让人兴奋的功能. 如多层堆或者压缩堆

PS: 多层堆即对象置于DRAM和冷对象置于NVMe闪存

3.其他了解

  • unicode10
  • Deprecate The Pack200 Tools and API
  • 新的Epsilon垃圾收集器
  • 完全支持Linux容器,包括Docker
  • 支持G1上的并行完全垃圾收集
  • 最新的HTTPS安全协议TLS 1.3
  • JAVA Flight Recoder

JDK12特性

一、JAVA12概述

  2019年3月19日,java12正式发布了,总共有8个新的JEP(JDK Enhancement Proposals)

JDK 12 is the open-source reference implementation of version 12 of the Java SE12 Platform as specified by by JSR 386 in the Java Community Process. JDK 12 reached General Availability on 19 March 2019. Production-ready binaries under the GPL are available from Oracle; binaries from other vendors will follow shortly. The features and schedule of this release were proposed and tracked via the JEP Process, as amended by the JEP 2.0 proposal. The release was produced using the JDK Release Process(JEP 3).

JAVA12的版本特性地址:openjdk.java.net/projects/jd…

相关特性的介绍

189:Shenandoah:A Low-Pause-Time Garbage Collector(Experimental) 低暂停时间的GC openjdk.java.net/jeps/189 230:Microbenchmark Suite 微基准测试套件 openjdk.java.net/jeps/230 325:Switch Expressions(Preview) switch表达式 openjdk.java.net/jeps/325 334:JVM Constants API JVM常量API openjdk.java.net/jeps/334 340:One AArch64 Port,Not Two 只保留一个AArch64实现 openjdk.java.net/jeps/340 341:Default CDS Archives 默认类数据共享归档文件 openjdk.java.net/jeps/341 344:Abortable Mixed Collections for G1 可中止的G1 Mixed GC openjdk.java.net/jeps/344 346:Promptly Return Unused Committed Memory from G1 G1及时返回未使用的已分配内存 openjdk.java.net/jeps/346

二、语法层次的改变

1. switch 表达式(预览)

  传统的switch声明语句(switch statement)在使用中有一些问题:

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

Java 12提供增强版的 switch 语句或称为 "switch 表达式"来写出更加简化的代码。

什么是预览?

  Switch 表达式也是作为预览语言功能的第一个语言改动被引入新版 Java 中来的,这是一种引入新特性的测试版的方法。通过这种方式,能够根据用户反馈进行升级、更改.如果没有被很好的接纳,则可以完全删除该功能。预览功能的没有被包含在Java SE 规范中。也就时说: 这不是一个正式的语法,是暂时进行测试的一种语法.

switch详细语法

  扩展的 switch 语句,不仅可以作为语句(statement),还可以作为表达式(expression),并且两种写法都可以,使用传统的 switch 语法,或者使用简化的“case L ->”模式匹配语法作用于不同范围并控制执行流。这些更改将简化日常编码工作,并为 switch 中的模式匹配(JEP 305)做好准备。

  • 使用 Java 12 中 switch 表达式的写法,省去了 break 语句,避免了因少写 break 而出错。
  • 同时将多个 case 合并到一行,显得简洁、清晰也更加优雅的表达逻辑分支,其具体写法就是将之前的 case 语句表成了:case L ->,即如果条件匹配 case ,则执行标签右侧的代码 ,同时标签右侧的代码段只能是表达式、代码块或 throw 语句。
  • 为了保持兼容性,case 条件语句中依然可以使用字符 : ,这时 fall-through 规则依然有效的,即不能省略原有的 break 语句,但是同一个 switch 结构里不能混用 -> 和 : ,否则会有编译错误。并且简化后的 switch 代码块中定义的局部变量,其作用域就限制在代码块中,而不是蔓延到整个 Switch 结构,也不用根据不同的判断条件来给变量赋值。

JAVA12之前switch语法的使用:

    public static void main(String[] args) {
        Month month=Month.APRIL;
        String season;
        switch (month){
            case DECEMBER:
            case JANUARY:
            case FEBRUARY:
                season="冬";
                break;
            case MARCH:
            case APRIL:
            case MAY:
                season="春";
                break;
            case JUNE:
            case JULY:
            case AUGUST:
                season="夏";
                break;
            case SEPTEMBER:
            case OCTOBER:
            case NOVEMBER:
                season="秋";
                break;
            default:
                throw new RuntimeException("NoSuchMonthException");
        }
        System.out.println(season);
    }

    public static void main(String[] args) {
        Month month=Month.APRIL;
        String season;
        switch (month){
            case DECEMBER,JANUARY,FEBRUARY ->season="冬";
            case MARCH,APRIL,MAY -> season="春";
            case JUNE,JULY,AUGUST -> season="夏";
            case SEPTEMBER,OCTOBER,NOVEMBER -> season="秋";
            default -> throw new RuntimeException("无效数据");
        }
        System.out.println(season);
    }

  似乎可以看出,JAVA开发者或将逐渐的从复杂繁琐的底层抽象代码的编写中解放出来,编写一些更高层次更优雅的代码. 减少出错,提高开发效率. 目前switch表达式支持下面的数据类型, byte char short int Byte, Character,Short,Integer,enum,String,未来是否会支持 float double和long? 目前本版本未对支持的数据类型进行拓展.

三、API层次的改变

1.支持数字压缩格式化

  NumberFormat 添加了对以紧凑形式格式化数字的支持。紧凑数字格式是指以简短或人类可读形式表示的数字。例如,在en_US语言环境中,1000可以格式化为“1K”,1000000可以格式化为“1M”,具体取决于指定的样式NumberFormat.Style。

var cnf = NumberFormat.getCompactNumberInstance(Locale.CHINA,
NumberFormat.Style.SHORT);
System.out.println(cnf.format(1_0000));
System.out.println(cnf.format(1_9200));
System.out.println(cnf.format(1_000_000));
System.out.println(cnf.format(1L << 30));
System.out.println(cnf.format(1L << 40));
System.out.println(cnf.format(1L << 50));

2. String新方法

String#transform(Function) : 它提供的函数作为输入提供给特定的String实例,并返回该函数返回的输出。

    @Test
    public  void test04() {
        var result = "linhao".transform(input -> input + "烤鸭");
        System.out.println(result); //林浩烤鸭

        result = "linhao"
                .transform(input -> input + " kaoya")
                .transform(String::toUpperCase);
        System.out.println(result);
    }

输出结果:

image-20240322094334511.png transform源码分析:

    /**
     * This method allows the application of a function to {@code this}
     * string. The function should expect a single String argument
     * and produce an {@code R} result.
     * <p>
     * Any exception thrown by {@code f.apply()} will be propagated to the
     * caller.
     *
     * @param f    a function to apply
     *
     * @param <R>  the type of the result
     *
     * @return     the result of applying the function to this string
     *
     * @see java.util.function.Function
     *
     * @since 12
     */
    public <R> R transform(Function<? super String, ? extends R> f) {
        return f.apply(this);
    }

  传入一个函数式接口 Function,接受一个值,返回一个值,连续调用transform方法,对字符串进行连续三次的改变.

    @Test
    public  void test05() {
        List<String> list1 = List.of("Java", " Golang", " Python ");
        List<String> list2 = new ArrayList<>();
        list1.forEach(element -> list2.add(element.transform(String::strip)
                .transform(String::toUpperCase)
                .transform((e) -> "Hello," + e))
        );
        list2.forEach(System.out::println);
    }

结果为:

  感觉和StreamAPI中的map功能和语法上有些类似,但是这里毕竟不是Stream,StreamAPI如果对元素进行改变使用map方法完成上述功能

    public static void main(String[] args) {
        List<String> list1 = List.of("Java ", " Golang", " Python ");
        Stream<String> stringStream = list1.stream().map(element ->
                element.strip()).map(String::toUpperCase).map(element -> "yeah!," + element);
        List<String> list2 = stringStream.collect(Collectors.toList());
        list2.forEach(System.out::println);
    }

输出结果:

image-20240322094610995.png

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

    public static void main(String[] args) {
        String result = "Java\nGolang\nPython".indent(3);
        System.out.println(result);
    }

image-20240322094700580.png 换行符 \n 后向前缩进 n 个空格,为 0 或负数不缩进。

indent源码:

    public String indent(int n) {
        if (isEmpty()) {
            return "";
        }
        Stream<String> stream = lines();
        if (n > 0) {
            final String spaces = " ".repeat(n);
            stream = stream.map(s -> spaces + s);
        } else if (n == Integer.MIN_VALUE) {
            stream = stream.map(s -> s.stripLeading());
        } else if (n < 0) {
            stream = stream.map(s -> s.substring(Math.min(-n, s.indexOfNonWhitespace())));
        }
        return stream.collect(Collectors.joining("\n", "", "\n"));
    }

3.Files新增mismatch方法

mismatch方法:对比两个文件的差异,返回从哪个字节开始出现了不一致

FileWriter fileWriter = new FileWriter("d:/a.txt");
fileWriter.write("a");
fileWriter.write("b");
fileWriter.write("c");
fileWriter.close();
FileWriter fileWriterB = new FileWriter("d:/b.txt");
fileWriterB.write("a");
fileWriterB.write("1");
fileWriterB.write("c");
fileWriterB.close();
System.out.println(Files.mismatch(Path.of("d:/a.txt"),Path.of("d:/b.txt")));

四、关于GC的特性

1.Shenandoah GC

  Shenandoah GC:低停顿时间的GC(预览):Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目 Pauseless GC 的实现,旨在针对 JVM 上的内存收回实现低停顿的需求。该设计将与应用程序线程并发,通过交换 CPU 并发周期和空间以改善停顿时间,使得垃圾回收器执行线程能够在 Java 线程运行时进行堆压缩,并且标记和整理能够同时进行,因此避免了在大多数 JVM 垃圾收集器中所遇到的问题。据 Red Hat 研发 Shenandoah 团队对外宣称,Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。与其他 Pauseless GC 类似,Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等。这是一个实验性功能,不包含在默认(Oracle)的OpenJDK版本中。

STW stop the world

Stop-the-World ,简称STW ,指的是GC 事件发生过程中,停止所有的应用程序线程的执行.

  垃圾回收器的任务是识别和回收垃圾对象进行内存清理。垃圾回收要求系统进入一个停顿的状态。停顿的目的是终止所有应用程序的执行,这样才不会有新的垃圾产生,同时保证了系统状态在某一个瞬间的一致性,并且有益于垃圾回收器更好地标记垃圾对象。停顿产生时整个应用程序会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW 。   如果Stop-the- World 出现在新生代的Minor GC 中时, 由于新生代的内存空间通常都比较小,所以暂停的时间较短,在可接受的范围内,老年代的Full GC 中时,程序的工作线程被暂停的时间将会更久。内存空间越大,执行Full GC 的时间就会越久,工作线程被暂停的时间也就会更长。到目前为止,哪怕是G1 也不能完全避免Stop-the-world 情况发生,只能说垃圾回收器越来越优秀,尽可能地缩短了暂停时间。

java垃圾收集器的分类 常见的Serial(串行) ParNEW(并行) Parallel(吞吐优先并行) CMS(低延迟) G1(区域化分代),不同的垃圾收集器都有自己的特征,简单进行一个分类

按线程数分类:

1、串行垃圾回收器

  串行回收指的是在同一时间段内只允许一件事情发生,简单来说,当多个CPU 可用时,也只能有一个CPU 用于执行垃圾回收操作,井且在执行垃圾回收时,程序中的工作线程将会被暂停,当垃圾收集工作完成后 才会恢复之前被暂停的工作线程,这就是串行回收。

image.png

2、并行垃圾回收器

  和串行回收相反,并行收集可以运用多个CPU 同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回 收仍然与串行回收一样,采用独占式,使用了“ Stop-the-world ”机制和复制算法

image.png

按照工作模式分类:

1 并发式垃圾收集器

  并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间。

2 独占式垃圾收集器

  独占式垃圾回收器( Stop the world)一旦运行,就停止应用程序中的其他所有线程,直到垃圾回收过程完 全结束。

image.png

按照碎片处理方式:

1、压缩式垃圾回收器;:压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片。

2、非压缩式垃圾回收器

非压缩式的垃圾回收器不进行这步操作。

按照工作的内存区间:

1、年轻代垃圾回收器

2、老年代垃圾回收器

如何判断垃圾回收器的性能:

  • 吞吐量:程序的运行时间(程序的运行时间+内存回收的时间)。
  • 垃圾收集开销:吞吐量的补数,垃圾收集器所占时间与总时间的比例。
  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间,就是用户功能延迟的时间。
  • 收集频率:相对于应用程序的执行,收集操作发生的频率。
  • 堆空间: Java 堆区所占的内存大小。
  • 快速: 一个对象从诞生到被回收所经历的时间。

  垃圾收集器中吞吐量和低延迟这两个目标本身是相互矛盾的,因为如果选择以吞吐量优先(单位时间希望运行更多的应用程序),那么必然需要降低内存回收的执行频率,但是这样会导致GC 需要更长的暂停时间来执行内存回收。相反的,如果选择以低延迟优先为原则,那么为了降低每次执行内存回收时的暂停时间,也只能频繁地执行内存回收,但这又引起了年轻代内存的缩减和导致程序吞吐量的下降。

Shenandoah工作原理:

从原理的角度,我们可以参考该项目官方的示意图,其内存结构与 G1 非常相似,都是将内存划分为类似棋盘的 region。整体流程与 G1 也是比较相似的,最大的区别在于实现了并发的 疏散(Evacuation) 环节,引入的 Brooks Forwarding Pointer 技术使得 GC 在移动对象时,对象引用仍然可以访问。

G1 内存设计:

image.png

Shenandoah GC 工作周期如下所示:

image.png

  1. Init Mark 启动并发标记阶段
  2. 并发标记遍历堆阶段
  3. 并发标记完成阶段
  4. 并发整理回收无活动区域阶段
  5. 并发 Evacuation 整理内存区域阶段
  6. Init Update Refs 更新引用初始化 阶段
  7. 并发更新引用阶段
  8. Final Update Refs 完成引用更新阶段
  9. 并发回收无引用区域阶段

  了解 Shenandoah GC 的人比较少,提及比较多的是 Oracle 在 JDK11 中开源出来的 ZGC,或者商业版本的 Azul C4(Continuously Concurrent Compacting Collector)。也有人认为Shenandoah 其实际意义大于后两者,原因有一下几点:

  1. 使用 ZGC 的最低门槛是升级到 JDK11,版本的更新不是一件容易的事情,而且 ZGC 实际表现如何也是尚不清楚。
  2. C4 成本较高,很多企业甚至斤斤计较几百元的软件成本。
  3. Shenandoah GC 可是有稳定的 JDK8u 版本发布的。甚至已经有公司在 HBase 等高实时性产品中有较多的实践。
  4. ZGC也是面向low-pause-time的垃圾收集器,不过ZGC是基于colored pointers来实现,而Shenandoah GC是
  5. 基于brooks pointers来实现。

  不是唯有 GC 停顿可能导致常规应用程序响应时间比较长。具有较长的 GC 停顿时间会导致系统响应慢的问题,但响应时间慢并非一定是 GC 停顿时间长导致的,队列延迟、网络延迟、其他依赖服务延迟和操作提供调度程序抖动等都可能导致响应变慢。使用 Shenandoah 时需要全面了解系统运行情况,综合分析系统响应时间。下面是jbb15benchmark 中,Shenandoah GC 相对于其他主流 GC 的表现。

各种 GC 工作负载对比:

image.png

  GC 暂停相比于 CMS 等选择有数量级程度的提高,对于 GC 暂停非常敏感的场景,价值还是很明显的,能够在 SLA层面有显著提高。当然,这种对于低延迟的保证,也是以消耗 CPU 等计算资源为代价的,实际吞吐量表现也不是非常明朗,需要看企业的实际场景需求,并不是一个一劳永逸的解决方案。

-XX:+AlwaysPreTouch:使用所有可用的内存分页,减少系统运行停顿,为避免运行时性能损失。
  
-Xmx == -Xmsv:设置初始堆大小与最大值一致,可以减轻伸缩堆大小带来的压力,与 AlwaysPreTouch 参数配
合使用,在启动时提交所有内存,避免在最终使用中出现系统停顿。
  
-XX:+ UseTransparentHugePages:能够大大提高大堆的性能,同时建议在 Linux 上使用时将
/sys/kernel/mm/transparent_hugepage/enabled 和
/sys/kernel/mm/transparent_hugepage/defragv 设置为:madvise,同时与 AlwaysPreTouch 一起使
用时,init 和 shutdownv 速度会更快,因为它将使用更大的页面进行预处理。
  
-XX:+UseNUMA:虽然 Shenandoah 尚未明确支持 NUMA(Non-Uniform Memory Access),但最好启用此功
能以在多插槽主机上启用 NUMA 交错。与 AlwaysPreTouch 相结合,它提供了比默认配置更好的性能。
  
-XX:+DisableExplicitGC:忽略代码中的 System.gc() 调用。当用户在代码中调用 System.gc() 时会强制
Shenandoah 执行 STW Full GC ,应禁用它以防止执行此操作,另外还可以使用 
  
-XX:+ExplicitGCInvokesConcurrent,在 调用 System.gc() 时执行 CMS GC 而不是 Full GC,建议在有
System.gc() 调用的情况下使用。
不过目前 Shenandoah 垃圾回收器还被标记为实验项目,如果要使用Shenandoah GC需要编译时--with-jvmfeatures
选项带有shenandoahgc,然后启动时使用参数
  
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

2.可中断的 G1 Mixed GC

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

  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 垃圾收集器不能递增处理的 GC 回收集的部分(如:年轻代),同时也可以包含老年代以提高处理效率。
  • 将 GC 回收集拆分为必需和可选部分时,垃圾收集过程优先处理必需部分。同时,需要为可选 GC 回收集部分维护一些其他数据,这会产生轻微的 CPU 开销,但小于 1 %的变化,同时在 G1 回收器处理 GC 回收集期间,本机内存使用率也可能会增加,使用上述情况只适用于包含可选 GC 回收部分的 GC 混合回收集合。
  • 在 G1 垃圾回收器完成收集需要必需回收的部分之后,如果还有时间的话,便开始收集可选的部分。但是粗粒度的处理,可选部分的处理粒度取决于剩余的时间,一次只能处理可选部分的一个子集区域。在完成可选收集部分的收集后,G1 垃圾回收器可以根据剩余时间决定是否停止收集。如果在处理完必需处理的部分后,剩余时间不足,总时间花销接近预期时间,G1 垃圾回收器也可以中止可选部分的回收以达到满足预期停顿时间的目标。

3.增强G1

G1概述

  上面介绍了 Java 12 中增强了 G1 垃圾收集器关于混合收集集合的处理策略,这节主要介绍在 Java 12 中同时也对 G1垃圾回收器进行了改进,使其能够在空闲时自动将 Java 堆内存返还给操作系统,这也是 Java 12 中的另外一项重大改进。

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

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

具体操作

  为了尽可能的向操作系统返回空闲内存,G1 垃圾收集器将在应用程序不活动期间定期生成或持续循环检查整体 Java堆使用情况,以便 G1 垃圾收集器能够更及时的将 Java 堆中不使用内存部分返还给操作系统。**对于长时间处于空闲状态的应用程序,此项改进将使 JVM 的内存利用率更加高效。而在用户控制下,可以可选地执行Full GC,以使返回的内存量最大化。

  JDK12的这个特性新增了两个参数分别是G1 PeriodicGCInterval及G1 PeriodicGCSystemLoadThreshold,设置为0的话,表示禁用。如果应用程序为非活动状态,在下面两种情况任何一个描述下,G1 回收器会触发定期垃圾收集:

  • 自上次垃圾回收完成以来已超过 G1PeriodicGCInterval ( milliseconds ), 并且此时没有正在进行的垃圾回收 任务。如果 G1PeriodicGCInterval 值为零表示禁用快速回收内存的定期垃圾收集。
  • 应用所在主机系统上执行方法 getloadavg(),默认一分钟内系统返回的平均负载值低于 G1PeriodicGCSystemLoadThreshold指定的阈值,则触发full GC或者concurrent GC( 如果开启 G1PeriodicGCInvokesConcurrent ),GC之后Java heap size会被重写调整,然后多余的内存将会归还给操作 系统。如果 G1PeriodicGCSystemLoadThreshold 值为零,则此条件不生效。

  如果不满足上述条件中的任何一个,则取消当期的定期垃圾回收。等一个 G1PeriodicGCInterval 时间周期后,将重新考虑是否执行定期垃圾回收。

  G1 定期垃圾收集的类型根据 G1PeriodicGCInvokesConcurrent 参数的值确定:如果设置值了,G1 垃圾回收器将继续上一个或者启动一个新并发周期;如果没有设置值,则 G1 回收器将执行一个Full GC。在每次一次 GC 回收末尾,   G1 回收器将调整当前的 Java 堆大小,此时便有可能会将未使用内存返还给操作系统。新的 Java 堆内存大小根据现有配置确定,具体包括下列配置:- XX:MinHeapFreeRatio、-XX:MaxHeapFreeRatio、-Xms、-Xmx。

  默认情况下,G1 回收器在定期垃圾回收期间新启动或继续上一轮并发周期,将最大限度地减少应用程序的中断。如果定期垃圾收集严重影响程序执行,则需要考虑整个系统 CPU 负载,或让用户禁用定期垃圾收集。

五、其他方面的特性

1.JVM常量API

  Java 12 中引入 JVM 常量 API,用来更容易地对关键类文件 (key class-file) 和运行时构件(artefact)的名义描述(nominal description) 进行建模,特别是对那些从常量池加载的常量,这是一项非常技术性的变化,能够以更简单、标准的方式处理可加载常量。

  具体来说就是java.base模块新增了java.lang.constant包。包中定义了一系列基于值的符号引用(JVMS 5.1)类型,它们能够描述每种可加载常量。

image.png

官方api链接地址:
http://cr.openjdk.java.net/~iris/se/12/latestSpec/api/java.base/java/lang/constant/package-summary.html

Java SE > Java SE Specifications > Java Virtual Machine Specification下的第5章:
Chapter 5. Loading, Linking, and Initializing
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html

  引入了ConstantDesc接口( ClassDesc、MethodTypeDesc、MethodHandleDesc这几个接口直接继承了ConstantDesc接口)以及Constable接口;ConstantDesc接口定义了resolveConstantDesc方法,Constable接口定义了describeConstable方法;String、Integer、Long、Float、Double均实现了这两个接口,而EnumDesc实现了ConstantDesc接口

image.png

  符号引用以纯 nominal 形式描述可加载常量,与类加载或可访问性上下文区分开。有些类可以作为自己的符号引用(例如 String)。而对于可链接常量,另外定义了一系列符号引用类型,具体包括: ClassDesc (Class 的可加载常量标称描述符) ,MethodTypeDesc(方法类型常量标称描述符) ,MethodHandleDesc (方法句柄常量标称描述符) 和DynamicConstantDesc (动态常量标称描述符) ,它们包含描述这些常量的 nominal 信息。此 API 对于操作类和方法的工具很有帮助。

ConstantDesc源码

/**A nominal descriptor for a loadable constant value, as defined in JVMS 4.4. Such a descriptor can be resolved via resolveConstantDesc(MethodHandles.Lookup) to yield the constant value itself.
Class names in a nominal descriptor, like class names in the constant pool of a classfile, must be interpreted with respect to a particular class loader, which is not part of the nominal descriptor.
Static constants that are expressible natively in the constant pool (String, Integer, Long, Float, and Double) implement ConstantDesc, and serve as nominal descriptors for themselves. Native linkable constants (Class, MethodType, and MethodHandle) have counterpart ConstantDesc types: ClassDesc, MethodTypeDesc, and MethodHandleDesc. Other constants are represented by subtypes of DynamicConstantDesc.
APIs that perform generation or parsing of bytecode are encouraged to use ConstantDesc to describe the operand of an ldc instruction (including dynamic constants), the static bootstrap arguments of dynamic constants and invokedynamic instructions, and other bytecodes or classfile structures that make use of the constant pool.
Constants describing various common constants (such as ClassDesc instances for platform types) can be found in ConstantDescs.
Implementations of ConstantDesc should be immutable and their behavior should not rely on object identity.
Non-platform classes should not implement ConstantDesc directly. Instead, they should extend DynamicConstantDesc (as Enum.EnumDesc and invoke.VarHandle.VarHandleDesc do.)
Nominal descriptors should be compared using the Object.equals(Object) method. There is no guarantee that any particular entity will always be represented by the same descriptor instance.
API Note:
In the future, if the Java language permits, ConstantDesc may become a sealed interface, which would prohibit subclassing except by explicitly permitted types. Clients can assume that the following set of subtypes is exhaustive: String, Integer, Long, Float, Double, ClassDesc, MethodTypeDesc, MethodHandleDesc, and DynamicConstantDesc; this list may be extended to reflect future changes to the constant pool format as defined in JVMS 4.4.
Since:
12
See Also:
Constable, ConstantDescs
*/
public interface ConstantDesc {
    /**
     * Resolves this descriptor reflectively, emulating the resolution behavior
     * of JVMS 5.4.3 and the access control behavior of JVMS 5.4.4.  The resolution
     * and access control context is provided by the {@link MethodHandles.Lookup}
     * parameter.  No caching of the resulting value is performed.
     *
     * @param lookup The {@link MethodHandles.Lookup} to provide name resolution
     *               and access control context
     * @return the resolved constant value
     * @throws ReflectiveOperationException if a class, method, or field
     * could not be reflectively resolved in the course of resolution
     * @throws LinkageError if a linkage error occurs
     *
     * @apiNote {@linkplain MethodTypeDesc} can represent method type descriptors
     * that are not representable by {@linkplain MethodType}, such as methods with
     * more than 255 parameter slots, so attempts to resolve these may result in errors.
     *
     * @jvms 5.4.3 Resolution
     * @jvms 5.4.4 Access Control
     */
    Object resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException;
}

Constable接口源码

/**
 * Represents a type which is <em>constable</em>.  A constable type is one whose
 * values are constants that can be represented in the constant pool of a Java
 * classfile as described in JVMS 4.4, and whose instances can describe themselves
 * nominally as a {@link ConstantDesc}.
 *
 * <p>Some constable types have a native representation in the constant pool:
 * {@link String}, {@link Integer}, {@link Long}, {@link Float},
 * {@link Double}, {@link Class}, {@link MethodType}, and {@link MethodHandle}.
 * The types {@link String}, {@link Integer}, {@link Long}, {@link Float},
 * and {@link Double} serve as their own nominal descriptors; {@link Class},
 * {@link MethodType}, and {@link MethodHandle} have corresponding nominal
 * descriptors {@link ClassDesc}, {@link MethodTypeDesc}, and {@link MethodHandleDesc}.
 *
 * <p>Other reference types can be constable if their instances can describe
 * themselves in nominal form as a {@link ConstantDesc}. Examples in the Java SE
 * Platform API are types that support Java language features such as {@link Enum},
 * and runtime support classes such as {@link VarHandle}.  These are typically
 * described with a {@link DynamicConstantDesc}, which describes dynamically
 * generated constants (JVMS 4.4.10).
 *
 * <p>The nominal form of an instance of a constable type is obtained via
 * {@link #describeConstable()}. A {@linkplain Constable} need
 * not be able to (or may choose not to) describe all its instances in the form of
 * a {@link ConstantDesc}; this method returns an {@link Optional} that can be
 * empty to indicate that a nominal descriptor could not be created for an instance.
 * (For example, {@link MethodHandle} will produce nominal descriptors for direct
 * method handles, but not necessarily those produced by method handle
 * combinators.)
 * @jvms 4.4 The Constant Pool
 * @jvms 4.4.10 The {@code CONSTANT_Dynamic_info} and {@code CONSTANT_InvokeDynamic_info} Structures
 *
 * @since 12
 */
public interface Constable {
    /**
     * Returns an {@link Optional} containing the nominal descriptor for this
     * instance, if one can be constructed, or an empty {@link Optional}
     * if one cannot be constructed.
     *
     * @return An {@link Optional} containing the resulting nominal descriptor,
     * or an empty {@link Optional} if one cannot be constructed.
     */
    Optional<? extends ConstantDesc> describeConstable();
}

String源码

/**
* Returns an {@link Optional} containing the nominal descriptor for this
* instance, which is the instance itself.
*
* @return an {@link Optional} describing the {@linkplain String} instance
* @since 12
*/
@Override
public Optional<String> describeConstable() {
return Optional.of(this);
}
/**
* Resolves this instance as a {@link ConstantDesc}, the result of which is
* the instance itself.
*
* @param lookup ignored
* @return the {@linkplain String} instance
* @since 12
*/
@Override
public String resolveConstantDesc(MethodHandles.Lookup lookup) {
return this;
}

测试代码

String name = "波波烤鸭";
Optional<String> optional = name.describeConstable();
System.out.println(optional.get());

测试结果

波波烤鸭

2.微基准测试套件

什么是JMH(java微基准测试)

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

应用场景

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

JMH使用

  要使用JMH,首先需要准备好Maven环境,

  如果要在现有Maven项目中使用JMH,只需要把生成出来的两个依赖以及shade插件拷贝到项目的pom中即可

<dependency>
	<groupId>org.openjdk.jmh</groupId>
	<artifactId>jmh-core</artifactId>
	<version>0.7.1</version>
</dependency>
<dependency>
	<groupId>org.openjdk.jmh</groupId>
	<artifactId>jmh-generator-annprocess</artifactId>
	<version>0.7.1</version>
	<scope>provided</scope>
</dependency>

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-shade-plugin</artifactId>
	<version>2.0</version>
	<executions>
		<execution>
			<phase>package</phase>
			<goals>
				<goal>shade</goal>
			</goals>
			<configuration>
				<finalName>microbenchmarks</finalName>
				<transformers>
   					 <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
						<mainClass>org.openjdk.jmh.Main</mainClass>
					</transformer>
				</transformers>
			</configuration>
		</execution>
	</executions>
</plugin>

新特性说明

Java 12 中添加一套新的基本的微基准测试套件(microbenchmarks suite),此功能为JDK源代码添加了一套微基准测试(大约100个),简化了现有微基准测试的运行和新基准测试的创建过程.使开发人员可以轻松运行现有的微基准测试并创建新的基准测试,其目标在于提供一个稳定且优化过的基准。 它基于Java Microbenchmark Harness(JMH),可以轻松测试JDK性能,支持JMH更新。

微基准套件与 JDK 源代码位于同一个目录中,并且在构建后将生成单个 jar 文件。但它是一个单独的项目,在支持构建期间不会执行,以方便开发人员和其他对构建微基准套件不感兴趣的人在构建时花费比较少的构建时间。

要构建微基准套件,用户需要运行命令:make build-microbenchmark, 类似的命令还有:make test TEST="micro:java.lang.invoke" 将使用默认设置运行java.lang.invoke 相关的微基准测试。

3.只保留一个AArch64实现

现状

  当前 Java 11 及之前版本JDK中存在两个64位ARM端口。这些文件的主要来源位于src/hotspot/cpu/arm 和 open/src/hotspot/cpu/aarch64 目录中。尽管两个端口都产生了aarch64 实现,我们将前者(由Oracle贡献)称为arm64 ,将后者称为aarch64 。

新特征

  Java 12 中将删除由 Oracle 提供的 arm64端口相关的所有源码,即删除目录 open/src/hotspot/cpu/arm 中关于64-bit 的这套实现,只保留其中有关 32-bit ARM端口的实现,余下目录的 open/src/hotspot/cpu/aarch64 代码部分就成了 AArch64 的默认实现。

目的

  这将使开发贡献者将他们的精力集中在单个 64 位 ARM 实现上,并消除维护两套实现所需的重复工作。

4.默认生成类的数据共享(CDS)归档文件

概述

  我们知道在同一个物理机/虚拟机上启动多个JVM时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的。所以Java团队引入了类数据共享机制 (Class Data Sharing ,简称 CDS) 的概念,通过把一些核心类在每个JVM间共享,每个JVM只需要装载自己的应用类即可。好处是:启动时间减少了,另外核心类是共享的,所以JVM的内存占用也减少了。

历史版本

1 JDK5引入了Class-Data Sharing可以用于多个JVM共享class,提升启动速度,最早只支持system classes及serial GC。 2 JDK9对其进行扩展以支持application classes(自定义class)及其他GC算法。 3 java10的新特性JEP 310: Application Class-Data Sharing扩展了JDK5引入的Class-Data Sharing,支持application的Class-Data Sharing并开源出来(以前是commercial feature)CDS 只能作用于 BootClassLoader 加载的类,不能作用于 AppClassLoader 或者自定义的 ClassLoader加载的类。在 Java 10 中,则将 CDS 扩展为 AppCDS,顾名思义,AppCDS 不止能够作用于BootClassLoader了,AppClassLoader 和自定义的 ClassLoader 也都能够起作用,大大加大了 CDS 的适用范围。也就说开发自定义的类也可以装载给多个JVM共享了。 4 JDK11将-Xshare:off改为默认-Xshare:auto,以更加方便使用CDS特性

迭代效果

  可以说,自 Java 8 以来,在基本 CDS 功能上进行了许多增强、改进,启用 CDS 后应用的启动时间和内存占用量显着减少。使用 Java 11 早期版本在 64 位 Linux 平台上运行 HelloWorld 进行测试,测试结果显示启动时间缩短有 32%,同时在其他 64 位平台上,也有类似或更高的启动性能提升。

JAVA12中的新特性

  JDK 12之前,想要利用CDS的用户,即使仅使用JDK中提供的默认类列表,也必须java -Xshare:dump 作为额外的步骤来运行。

  Java 12 针对 64 位平台下的 JDK 构建过程进行了增强改进,使其默认生成类数据共享(CDS)归档,以进一步达到改进应用程序的启动时间的目的,同时也避免了需要手动运行:java -Xshare:dump 的需要,修改后的 JDK 将在${JAVA_HOME}/lib/server 目录中生成一份名为classes.jsa的默认archive文件(大概有18M)方便大家使用。

  当然如果需要,也可以添加其他 GC 参数,来调整堆大小等,以获得更优的内存分布情况,同时用户也可以像之前一样创建自定义的 CDS 存档文件。

5.增加:支持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)

6.其他新增

  • Collectors新增teeing方法用于聚合两个downstream的结果

  • CompletionStage新增exceptionallyAsync、exceptionallyComposeAsync方法,允许方法体在异步线程执行,同时新增了exceptionallyCompose方法支持在exceptionally的时候构建新的CompletionStage。

  • ZGC: Concurrent Class Unloading

    • ZGC在JDK11的时候还不支持class unloading,JDK12对ZGC支持了Concurrent Class Unloading,默认是开启,使用-XX:-ClassUnloading可以禁用
  • 新增-XX:+ExtensiveErrorReports

    • -XX:+ExtensiveErrorReports可以用于在jvm crash的时候收集更多的报告信息到hs_err.log文件中,product builds中默认是关闭的,要开启的话,需要自己添加-XX:+ExtensiveErrorReports参数
  • 新增安全相关的改进

    • 支持java.security.manager系统属性,当设置为disallow的时候,则不使用SecurityManager以提升性能,如果此时调用System.setSecurityManager则会抛出UnsupportedOperationExceptionkeytool新增-groupname选项允许在生成key pair的时候指定一个named group新增PKCS12 KeyStore配置属性用于自定义PKCS12 keystores的生成Java Flight Recorder新增了security-related的event支持ChaCha20 and Poly1305 TLS Cipher Suites

7.移除项

移除com.sun.awt.SecurityWarnin; 移除FileInputStream、FileOutputStream、- Java.util.ZipFile/Inflator/Deflator的finalize方法; 移除GTE CyberTrust Global Root; 移除javac的-source, -target对6及1.6的支持,同时移除--release选项;

8.废弃项

废弃的API列表见deprecated-list 废弃-XX:+/-MonitorInUseLists选项 废弃Default Keytool的-keyalg值

JDK13特性

一、JAVA13概述

  2019年9月17日,国际知名的OpenJDK开源社区发布了Java编程语言环境的最新版本OpenJDK13。

Features:总共有5个新的JEP(JDK Enhancement Proposals): openjdk.java.net/projects/jd…

Features:

350:Dynamic CDS Archives:动态CDS档案 351:ZGC: Uncommit Unused Memory:ZGC:取消使用未使用的内存 353:Reimplement the Legacy Socket API:重新实现旧版套接字API 354:Switch Expressions (Preview):switch表达式(预览) 355:Text Blocks (Preview):文本块

二、语法层面特性

1.switch表达式(预览)

  在JDK 12中引入了switch表达式作为预览特性。JDK 13提出了第二个switch表达式预览。JEP 354修改了这个特性,它引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield, switch语句(不返回值)应该使用break。   在 JDK 12中有一个,但是要进行一个更改:要从 switch 表达式中生成一个值 break,要删除with value语句以支持ayield 声明。目的是扩展,switch 以便它可以用作语句或表达式,因此两个表单既可以使用 case ... : 带有连贯符号的传统标签,也可以使用新 case … -> 标签,而不需要通过,还有一个新的语句用于从 switch 表达式中产生值。这些更改将简化编码并为模式匹配做好准备。

  在以前,我们想要在switch中返回内容,还是比较麻烦的,一般语法如下:

String x = "3";
int i;
switch (x) {
	case "1":
		i=1;
		break;
	case "2":
		i=2;
		break;
	default:
		i = x.length();
		break;
}
System.out.println(i);

在JDK13中使用以下语法:

String x = "3";
int i = switch (x) {
	case "1" -> 1;
	case "2" -> 2;
	default -> {
		yield 3;
	}
};
System.out.println(i);

或者:

String x = "3";
int i = switch (x) {
	case "1":
		yield 1;
	case "2":
		yield 2;
	default:
		yield 3;
};
System.out.println(i);

  在这之后,switch中就多了一个关键字用于跳出switch块了,那就是yield,他用于返回一个值。和return的区别在于:return会直接跳出当前循环或者方法,而yield只会跳出当前switch块。

2.文本块(预览)

2.1 概念

  在JDK 12中引入了Raw String Literals特性,但在发布之前就放弃了。这个JEP与引入多行字符串文字(text block)在意义上是类似的。

  这条新特性跟 Kotlin 里的文本块是类似的。

2.2 问题

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

2.3 目标

  简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写Java程序。 增强Java程序中字符串的可读性。

定义一段HTML代码

<html>
	<body>
		<a href="http://www.baidu.com">百度</a>
	</body>
</html>

将这段代码放入java的String中,会出现如下效果

String words =
    "<html>\n" +
	"\t<body>\n" +
	"\t\t<a href="http://www.baidu.com">百度</a>\n" +
	"\t</body>\n" +
	"</html>";

自动将空格换行缩进和特殊符号进行了转义,但是在JDK13中可以使用这样的语法了:

String words = """
    <html>
        <body>
           <a href="http://www.baidu.com">百度</a>
        </body>
    </html>""";

使用"""作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。看起来就十分清爽了。

如常见的SQL语句:

select empno,ename,sal,deptno
from emp
where deptno in (40,50,60)
order by deptno asc

原来的方式

String query = "select empno,ename,sal,deptno\n" +
"from emp\n" +
"where deptno in (40,50,60)\n" +
"order by deptno asc";

现在方式

String newQuery = """
select empno,ename,sal,deptno
from emp
where deptno in (40,50,60)
order by deptno asc
""";

2.4 语法细节1 基本使用

  • 文本块是Java语言中的一种新文字。它可以用来表示任何字符串,并且提供更大的表现力和更少的复杂性。
  • 文本块由零个或多个字符组成,由开始和结束分隔符括起来。
    • 开始分隔符是由三个双引号字符("""),后面可以跟零个或多个空格,最终以行终止符结束。文本块内容以开始分隔符的行终止符后的第一个字符开始。
    • 结束分隔符也是由三个双引号字符(""")表示,文本块内容以结束分隔符的第一个双引号之前的最后一个字符结束。
  • 文本块中的内容可以直接使用","但不是必需的。
  • 文本块中的内容可以直接包括行终止符。允许在文本块中使用 \n,但不是必需的。例如,文本块:
"""
line1
line2
line3
"""

相当于

"line1\nline2\nline3\n"

或者相当于一个字符串用+拼接

"line1\n" +
"line2\n" +
"line3\n"

文本块可以表示空字符串,但不建议这样做,因为它需要两行源代码:

String empty = """
""";

以下是错误格式的文本块

String a = """"""; // 开始分隔符后没有行终止符
String b = """ """; // 开始分隔符后没有行终止符
String c = """
"; // 没有结束分隔符
String d = """
abc \ def
"""; // 含有未转义的反斜线(请参阅下面的转义处理)

在运行时,文本块将被实例化为String的实例,就像字符串一样。从文本块派生的String实例与从字符串派生的实例是无法区分的。具有相同内容的两个文本块将引用相同的String实例,就像字符串一样。

2.5 语法细节2 编译器在编译时,会删除多余的空格

下面这段代码中,我们用.来表示我们代码中的的空格,而这些位置的空格就是多余的。

String html = """
..............<html>
.............. <body>
.............. <p>Hello, world</p>
.............. </body>
..............</html>
..............""";

多余的空格还会出现在每一行的结尾,特别是当你从其他地方复制过来时,更容易出现这种情况,比如下面的代码:

String html = """
..............<html>...
.............. <body>
.............. <p>Hello, world</p>....
.............. </body>.
..............</html>...
..............""";

PS:每行文字后面的空格,编译器会自动帮助我们去掉,但是开头部分的空格和结束的"""; 前面的空格数有关, """;前面有几个空格,就会自动帮助我们去掉每一行前面的几个空格

  这些多余的空格对于程序员来说是看不到的,但是他又是实际存在的,所以如果编译器不做处理,可能会导致程序员看到的两个文本块内容是一样的,但是这两个文本块却因为存在这种多余的空格而导致差异,比如哈希值不相等。

2.6 语法细节3 转义字符

  允许开发人员使用 \n,\f 和\r 来进行字符串的垂直格式化,使用 \b和 \t进行水平格式化。比如下面的代码是合法的:

String html = """
<html>\n
<body>\n
<p>Hello, world</p>\n
</body>\n
</html>\n
""";

请注意,在文本块内自由使用"是合法的。例如:

String story = """
"When I use a word," Humpty Dumpty said,
in rather a scornful tone, "it means just what I
choose it to mean - neither more nor less."
"The question is," said Alice, "whether you
can make words mean so many different things."
"The question is," said Humpty Dumpty,
"which is to be master - that's all."
""";

但是,三个"字符的序列需要进行转义至少一个"以避免模仿结束分隔符:

String code =
"""
String text = \"""
A text block inside a text block
\""";
""";

2.7 语法细节4 文本块连接

  可以在任何可以使用字符串的地方使用文本块。例如,文本块和字符串可以相互连接:

String code = "public void print(Object o) {" +
"""
System.out.println(Objects.toString(o));
}
""";

  但是,涉及文本块的连接可能变得相当笨重。以下面文本块为基础:

String code = """
public void print(Object o) {
System.out.println(Objects.toString(o));
}
""";

  假设我们想把上面的Object改为来自某一变量,我们可能会这么写:

String code = """
public void print(""" + type + """
o) {
System.out.println(Objects.toString(o));
}
""";

  可以发现这种写法可读性是非常差的,更简洁的替代方法是使用String :: replace或String :: format,比如:

String code = """
public void print($type o) {
System.out.println(Objects.toString(o));
}
""".replace("$type", type);
String code = String.format("""
public void print(%s o) {
System.out.println(Objects.toString(o));
}
""", type);

另一个方法是使用String :: formatted,这是一个新方法,比如:

String source = """
public void print(%s object) {
System.out.println(Objects.toString(object));
}
""".formatted(type);

三、API层次特性

1.重新实现旧版套接字API

目前的问题

重新实现了古老的 Socket 接口。现在已有的 java.net.Socket 和 java.net.ServerSocket 以及它们的实现类,都可以回溯到 JDK 1.0 时代了。

  • 它们的实现是混合了 Java 和 C 的代码的,维护和调试都很痛苦。
  • 实现类还使用了线程栈作为 I/O 的缓冲,导致在某些情况下还需要增加线程栈的大小。
  • 支持异步关闭,此操作是通过使用一个本地的数据结构来实现的,这种方式这些年也带来了潜在的不稳定性和跨平台移植问题。该实现还存在几个并发问题,需要彻底解决。

在未来的网络世界,要快速响应,不能阻塞本地方法线程,当前的实现不适合使用了。

新的实现类

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

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

代码说明

运行一个实例化Socket和ServerSocket的类将显示这个调试输出。这是默认的(新的)。

/**
 * The abstract class {@code SocketImpl} is a common superclass
 * of all classes that actually implement sockets. It is used to
 * create both client and server sockets.
 *
 * @implNote Client and server sockets created with the {@code Socket} and
 * {@code SocketServer} public constructors create a system-default
 * {@code SocketImpl}. The JDK historically used a {@code SocketImpl}
 * implementation type named "PlainSocketImpl" that has since been replaced by a
 * newer implementation. The JDK continues to ship with the older implementation
 * to allow code to run that depends on unspecified behavior that differs between
 * the old and new implementations. The old implementation will be used if the
 * Java virtual machine is started with the system property {@systemProperty
 * jdk.net.usePlainSocketImpl} set to use the old implementation. It may also be
 * set in the JDK's network configuration file, located in {@code
 * ${java.home}/conf/net.properties}. The value of the property is the string
 * representation of a boolean. If set without a value then it defaults to {@code
 * true}, hence running with {@code -Djdk.net.usePlainSocketImpl} or {@code
 * -Djdk.net.usePlainSocketImpl=true} will configure the Java virtual machine
 * to use the old implementation. The property and old implementation will be
 * removed in a future version.
 *
 * @since   1.0
 */
public abstract class SocketImpl implements SocketOptions {
    private static final boolean USE_PLAINSOCKETIMPL = usePlainSocketImpl();

    private static boolean usePlainSocketImpl() {
        PrivilegedAction<String> pa = () -> NetProperties.get("jdk.net.usePlainSocketImpl");
        String s = AccessController.doPrivileged(pa);
        return (s != null) && !s.equalsIgnoreCase("false");
    }

SocketImpl的USE_PLAINSOCKETIMPL取决于usePlainSocketImpl方法,而它会从NetProperties读取 jdk.net.usePlainSocketImpl配置,如果不为null且不为false,则usePlainSocketImpl方法返回true; createPlatformSocketImpl会根据USE_PLAINSOCKETIMPL来创建PlainSocketImpl或者NioSocketImpl。

四、其他变化

1. ZGC取消为使用的内存

G1和Shenandoah:

JVM的GC释放的内存会还给操作系统吗? GC后的内存如何处置,其实是取决于不同的垃圾回收器。因为把内存还给OS,意味着要调整JVM的堆大小,这个过程是比较耗费资源的。

  • Java12的346: Promptly Return Unused Committed Memory from G1新增了两个参数分别是 G1PeriodicGCInterval及G1PeriodicGCSystemLoadThreshold用于GC之后重新调整Java heap size,然后将多余的内存归还给操作系统
  • Java12的189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)拥有参数 -XX:ShenandoahUncommitDelay=来指定ZPage的page cache的失效时间,然后归还内存

HotSpot的G1和Shenandoah这两个GC已经提供了这种能力,并且对某些用户来说,非常有用。因此,Java13则给ZGC新增归还unused heap memory给操作系统的特性。

ZGC的使用背景

  在JDK 11中,Java引入了ZGC,这是一款可伸缩的低延迟垃圾收集器,但是当时只是实验性的。号称不管你开了多大的堆内存,它都能保证在 10 毫秒内释放 JVM ,不让它停顿在那。但是,当时的设计是它不能把内存归还给操作系统。对于比较关心内存占用的应用来说,肯定希望进程不要占用过多的内存空间了,所以这次增加了这个特性。

image.png

  在Java 13中,JEP 351再次对ZGC做了增强,将没有使用的堆内存归还给操作系统。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这个参数也能让这个内存管理能力失效。

2.动态CDS档案

作用

  在JAVA应用程序在程序执行结束时动态归档类. 归档的类将包括默认基层CDS归档中不存在的所有已加载应用程序类和类库

  CDS,是java 12的特性了,可以让不同 Java 进程之间共享一份类元数据,减少内存占用,它还能加快应用的启动速度。而JDK13的这个特性支持在Java application执行之后进行动态archive。存档类将包括默认的基础层CDS存档中不存在的所有已加载的应用程序和库类。也就是说,在Java 13中再使用AppCDS的时候,就不再需要这么复杂了。   该提案处于目标阶段,旨在提高AppCDS的可用性,并消除用户进行运行时创建每个应用程序的类列表的需要。

# JVM退出时动态创建共享归档文件:导出jsa
java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello
# 用动态创建的共享归档文件运行应用:使用jsa
java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello

目的

JAVA13 这次对CDS增强的目的

  • 改善APPCDS的可用性,减少用户每次都要创建一个类列表的需要
  • 通过开启 -Xshare:dump 选项来开启静态归档,使用类列表仍然行得通,包含内置的类加载信息和用户定义的类加载信息

意义

  在JDK13中做的增强,可以只开启命令行选项完成上述过程,在程序运行的时候,动态评估哪些类需要归档,同时支持内置的类加载器和用户定义的类加载器

  在第一次程序执行完成后,会自动的将类进行归档,后续启动项目的时候也无需指定要使用哪些归档,整个过程看起来更加透明

3.增加废弃和移除

增加项

  • 添加FileSystems.newFileSystem(Path, Map<String, ?>) Method
  • 新的java.nio.ByteBuffer Bulk get/put Methods Transfer Bytes Without Regard to Buffer Position
  • 支持Unicode 12.1
  • 添加-XX:SoftMaxHeapSize Flag,目前仅仅对ZGC起作用
  • ZGC的最大heap大小增大到16TB

移除项

  • 移除awt.toolkit System Property
  • 移除Runtime Trace Methods
  • 移除-XX:+AggressiveOpts
  • 移除Two Comodo Root CA Certificates、Two DocuSign Root CA Certificates
  • 移除内部的com.sun.net.ssl包

废弃项

  • 废弃-Xverify:none及-noverify
  • 废弃rmic Tool并准备移除
  • 废弃javax.security.cert并准备移除

JDK14特性

一、Java14概述

  Oracle在2020年3月17日宣布JAVA14 全面上市,JAVA14通过每六个月发布一次新功能,为企业和开发人员社区提供增强功能,继续了Oracle加快创新的承诺. 最新的JAVA开发工具包提供了新功能,其中包括两项备受期待的新预览功能,实例匹配的匹配模式(JEP 305) 和记录(JEP 359),以及文本块的第二个预览(JEP 368),此外,最新的JAVA版本增加了对switch表达式的语言支持,公开了,用于持续监控JDK Flight Recorder数据的新API,将低延迟的Z垃圾收集器的可用性扩招到了macOS和Windows,并在孵化器模块中添加了包装完备的java应用程序和新的外部内存访问API,以安全高效的访问JAVA对外部的内存

我们可以在openjdk官网中观察到JDK14发布的详细官方计划和具体新特性详情,地址如下

openjdk.java.net/projects/jd…

JAVA14 一共发行了16个JEP(JDK Enhancement Proposals,JDK 增强提案)

image.png

具体新增特性的介绍: 语言特性7项目:

  • switch表达式(标准)
  • 友好的空指针异常
  • 非易失性字节缓冲区
  • record
  • instanceof模式匹配
  • 文本块改进 二次预览
  • 外部存储API

垃圾回收调整:

  • G1的NUMA内存分配优化

新增工具:

  • JAVA打包工具 孵化
  • JFR事件流

增加废弃和移除:

  • MacOS系统上的ZGC试验
  • windows系统上的ZGC实验
  • 弃用Parallel Scavenge 和Serial Old垃圾收集算法
  • 弃用Solaris和SPCRC端口
  • 移除CMS垃圾收集器
  • 删除Pack200工具和API

二、语法层面的变化

1. instanceof

  以往我们使用instanceof运算符都是先判断,然后在进行强转,例如我们查看String的equals方法源码.

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        // 先进行类型的判断 
        if (anObject instanceof String) {
            // 然后进行强转
            String aString = (String)anObject;
            if (!COMPACT_STRINGS || this.coder == aString.coder) {
                return StringLatin1.equals(value, aString.value);
            }
        }
        return false;
    }

  需要先判断类型,然后强转,还要声明一个本地变量,语法比较麻烦.比较理想的状态是,在执行类型检测的时候同时执行类型转换

  JEP305 新增了使instanceof运算符具有匹配的能力. 模式匹配能够是程序的通用逻辑更加简洁,代码更加简单,同时在做类型判断和类型转换的时候也更加安全.详情如下

  JAVA14 提供了新的解决方案: 新的instanceof模式匹配,新的模式匹配语法是: 在instanceof的类型之后添加了变量. 如果对obj的类型检查通过,obj会被转换成后面的变量表示的数据类型. 数据类型的声明仅仅书写一次即可

Object obj ="hello java";
        if(obj instanceof String str){
            System.out.println(str);
        }else{
            System.out.println("not a String");
        }

  上述语法的判断逻辑时,如果obj是String类型,则会转换为后面的str,如果不是,则执行else,注意,此时的str仅仅是if语句块里的局部变量,在else语句块中不可用

        Object obj ="hello java";
        // 这里做的是取反运算
        if(!(obj instanceof String str) ){
            System.out.println("not a String");
            //System.out.println(str);// 这里不能使用str
        }else{
            System.out.println(str);// 这里可以使用str
        }

  但是如果if语句中使用了! 这种取反运算,那么逻辑上就是相反的,这个时候else才是相当于成功转换了,所以在else中可以使用str,if中不可以使用str

Object obj =new Date();// "hello java";
        if(obj instanceof String str && str.length()>2){
            System.out.println(str);
        }else{
            System.out.println("not a String or length <=2");
        }

  上述语句块中,如果if中的判断逻辑比较复杂,是可以在后续的其他条件中使用str变量进行判断的,但是注意这里的运算符是短路与运算,就是要保证后面在使用str时,已经完成了转换,如果使用短路或运算,无法保证str是可以成功转换的,是不允许的,如下面的代码,就是错的

Object obj =new Date();// "hello java";
        if(obj instanceof String str || str.length()>2){
            System.out.println(str);
        }else{
            System.out.println("not a String or length <=2");
        }

  总之: if语句块中的小括号内,要保证成功的进行了转换才可以在if语句库中使用转换的对象,否则不可以

  通过这个模式匹配,我们可以简化在类中重写的equals方法

import java.util.Objects;
class Person{
    private String pname;
    private Integer page;
    public Person(String pname,Integer page){
        this.pname =pname;
        this.page=page;
    }
    @Override
    public boolean equals(Object obj){
        return obj instanceof Person p && Objects.equals(this.pname,p.pname)&& Objects.equels(this.page,p.page);
    }
}

2. switch表达式

  java的Switch语句是一个一直在变化的语法,可能是因为之前的不够强大,在JAVA14中,我们依然可以看到对于switch的语法优化.

我们简单整理一下switch语句在各个版本中的特点

JAVA5 switch变量类型可以使用枚举了

JAVA7 switch变量类型中可以使用String

JAVA11 switch语句可以自动省略break导致的贯穿提示警告 case L ->

JAVA12 switch语句可以作为表达式,用变量接收结果,可以省略break

JAVA13 switch中可以使用yield关键字停止switch语句块

JAVA14 JEP361switch表达式(标准)是独立的,不依赖于JEP 325 和 JEP 354,也就是说这里开始,之前学习的switch语句的语法成为一个正式的标准.未来是否有更多的改进,我们可以拭目以待

JDK12对缺省break的贯穿弱点进行了改进,case: 改成 case L -> ,这样即使不写也不会贯穿了,而且可以作为表达式返回结果

var grade ="a";
var res =switch(grade){
        case "a" -> "优秀";
        case "b" -> "良好";
        case "c" -> "一般";
        case "d" -> "及格";
        default -> "no such grade";
}


JAVA12 开始也可以进行多值匹配的支持

var grade ="a";
var res =switch(grade){
        case "a","b" -> "优秀";
    case "c" -> "一般";
        case "d" -> "及格";
        default -> "no such grade";
}

JAVA13开始可以使用 yield返回结果,这里的case后面仍然是:

String x = "3";
int i = switch (x) {
case "1":
yield 1;
case "2":{
        System.out.println("");
        yield 2;
    }

default:
yield 3;
};
System.out.println(i);

3. 文本块的改进

Text Blocks(Second Preview)

问题

  文本块是在JAVA13中开始了第一次的预览,目标是在字符串中可以更好的表达 HTML XML SQL或者JSON格式的字符串,减少各种的不相关一些空格换行符号,字符串转义和字符串加号的拼接,在JAVA14中,增加了两个escape sequence ,分别是 \ <line-terminator>(取消换行操作) 与\s escape sequence(增加空格),文本块进行了第二次预览,进一步调了JAVA程序书写大段字符串文本的可读性和方便性

目标

  • 简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化java程序
  • 增强java程序中用字符串表示其他语言代码的可读性
  • 解析新的转义序列
String textBlock= """
               <!DOCTYPE html>
               <html lang="en">
               <head>
                   <meta charset="UTF-8">
                   <title>Title</title>
               </head>\s\
               <body>
                              
               </body>\s
               </html>
               """;
        System.out.println(textBlock);

4. Records记录类型

  通过Record增强java编程语言,Record提供了一种紧凑的语法来声明类,这些类是浅层不可变数据的透明持有者.

问题分析

  我们经常听到这样的抱怨:"JAVA太冗长","JAVA规矩多". 最明显的就是最为简单数据载体的类,为了写一个数据类,开发人员必须编写许多低价值,重复,且容易出错的代码,构造函数,getter setter访问器 , equals, hashcode, toString 这些东西,尽管IDE可以提供一些插件和手段优化,但是仍然没有改变这些代码依然存在,需要操作的事实

传统的类如下

class Person{
    private Integer pid;
    private String pname;
    private Integer page;

    @Override
    public String toString() {
        return "Person{" +
                "pid=" + pid +
                ", pname='" + pname + '\'' +
                ", page=" + page +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(pid, person.pid) && Objects.equals(pname, person.pname) && Objects.equals(page, person.page);
    }

    @Override
    public int hashCode() {
        return Objects.hash(pid, pname, page);
    }

    public Integer getPid() {
        return pid;
    }

    public void setPid(Integer pid) {
        this.pid = pid;
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public Integer getPage() {
        return page;
    }

    public void setPage(Integer page) {
        this.page = page;
    }

    public Person() {
    }

    public Person(Integer pid, String pname, Integer page) {
        this.pid = pid;
        this.pname = pname;
        this.page = page;
    }
}

就算是使用IDE的快捷键,这些代码也是臃肿的

Records记录类型语法

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

  一个record具有名称和状态描述,状态描述声明了record的组成部分

record Person(String name ,int age){}

因为record在与以上是数据的简单透明持有者,所以record会自动获取很多的标准成员

  • 状态声明中的每个成员,都是一个private final的字段,属性设置值则不可修改
  • 状态声明中的每个组件的公共读取访问方法,该方法和组件具有相同的名字,get方法和属性名一致
  • 一个公共的构造函数,其签名与状态声明相同,构造方法和签名合二为一
  • equals和hashcode的实现
  • toString的实现
  • record提供的默认是一个全参的构造器

测试代码如下

package com.msb.test;

import java.util.Objects;

public class Test2 {
    public static void main(String[] args) {
        Person p =new Person(1,"张三",10);
        Person p2 =new Person(1,"张三",10);
        System.out.println(p.pname());
        System.out.println(p);
        System.out.println(p.hashCode());
        System.out.println(p2.hashCode());
        System.out.println(p.equals(p2));
    }
}
record Person(Integer pid,String pname ,Integer page){};

测试结果如下

张三
Person[pid=1, pname=张三, page=10]
24022530
24022530
true

Records的一些限制

records类是隐含的final类,并且不是抽象类,records不能拓展任何类,不能被继承,声明的任何其他字段都必须是静态的,records的API仅仅能由其状态描述定义(通过属性定义)

在record中声明额外的变量类型

  也可以显示声明从状态描述自动派生的任何成员,可以在没有正式参数列表的情况下声明构造函数,并且在正常的构造函数主体正常完成是调用隐式初始化,这样就可以在显示构造函数中仅执行其参数的验证逻辑,并且省略字段的初始化

测试代码如下

package com.msb.test;

import java.util.Objects;

public class Test2 {
    public static void main(String[] args) {
        Person p =new Person(1,"张三",10);
        Person p2 =new Person(1,"张三",10);
        System.out.println(p.pname());
        System.out.println(p);
        System.out.println(p.hashCode());
        System.out.println(p2.hashCode());
        System.out.println(p.equals(p2));

    }
}
record Person(Integer pid,String pname ,Integer page){
    // 定义额外的变量必须是静态的,不能定义成员变量
    private static String name;
    public static void setName(String name){
        Person.name=name;
    }
    // 可以定义其他实例方法
    public void eat(){
        System.out.println("eat");
    }
    // 可以定义其他静态方法
    public static void methodA(){
        System.out.println("methoA");
    }
    // 这里是构造函数,默认就是全参的构造函数,和record声明的参数列表是一致的,
    // 这里可以使用全参构造函数中的所有参数
    // 在这里会默认执行参数给属性赋值操作,就是在这里默认会有this.pid=pid,this.pname=pname,this.page=page
    public Person{
        System.out.println(pid);
        System.out.println(pname);
        System.out.println(page);
    }
};

二、关于GC

1.G1的NUMA内存分配优化

NUMA-Aware Memory Allocation for G1

NUMA

  NUMA就是非统一内存访问架构(英语:non-uniform memory access,简称NUMA),是一种为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。在NUMA下,处理器访问它自己的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。如下图所示,Node0中的CPU如果访问Node0中的内存,那就是访问本地内存,如果它访问了Node1中的内存,那就是远程访问,性能较差:

image.png

非统一内存访问架构的特点是:

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

目标

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

  现代的multi-socket服务器越来越多都有NUMA,意思是,内存到每个socket的距离是不相等的,内存到不同的socket之间的访问是有性能差异的,这个距离越长,延迟就会越大,性能就会越差!(openjdk.java.net/jeps/345)

  G1的堆组织为固定大小区域的集合。一个区域通常是一组物理页面,尽管使用大页面(通过 -XX:+UseLargePages)时,多个区域可能组成一个物理页面。

  如果指定了+XX:+UseNUMA选项,则在初始化JVM时,区域将平均分布在可用NUMA节点的总数上。

  在开始时固定每个区域的NUMA节点有些不灵活,但是可以通过以下增强来缓解。为了为mutator线程分配新的对象,G1可能需要分配一个新的区域。它将通过从NUMA节点中优先选择一个与当前线程绑定的空闲区域来执行此操作,以便将对象保留在新生代的同一NUMA节点上。如果在为变量分配区域的过程中,同一NUMA节点上没有空闲区域,则G1将触发垃圾回收。要评估的另一种想法是,从距离最近的NUMA节点开始,按距离顺序在其他NUMA节点中搜索自由区域。

  该特性不会尝试将对象保留在老年代的同一NUMA节点上。

  JEP 345专门用于实现G1垃圾收集器的NUMA支持,仅用于内存管理(内存分配),并且仅在Linux下。对于NUMA体系结构的这种支持是否也适用于其他垃圾回收器或其他部分(例如任务队列窃取),尚不清楚。

2. 弃用Serial+CMS,ParNew+Serial Old

  由于维护和兼容性测试的成本,在JDK8时将Serial+CMS,ParNew+Serial Old这两个组合声明为废弃(JEP173),并在JDK9中完全取消了这些组合的支持(JEP214)

image.png

ParallelScavenge+SerialOld GC 的GC组合要被标记为Deprecate了

官方给出的理由

  这个GC组合需要大量的代码维护工作,并且,这个GC组合很少被使用.因为它的使用场景应该是一个很大的Young区和一个很小的Old区,这样的话,Old区用SerialOld GC去收集停顿时间才可以勉强被接受

  废弃了Parallelyoung generationGC 与SerialOldGC组合 (-XX:+UseParallelGC 与 -XX:-UseParallelOldGC 配合开启),现在使用-XX:+UseParallelGC -XX:-UseParallelOldGC或者使用 -XX:-UseParallelOldGC会出现如下警告

image.png

3.删除CMS

删除CMS垃圾回收器

  自从G1出现后,CMS在JDK9中就被标记为Deprecate了

CMS弊端

  • 会产生内存碎片,导致并发清除后,用户线程可用空间不足(标记清除算法产生,需要整理算法解决)
  • 既然强调了并发(Concurrent) CMS收集器对于CPU资源非常敏感,导致吞吐量降低
  • CMS收集器无法处理浮动垃圾(用户线程和垃圾回收线程并发执行,回收时用户线程产生新的垃圾)

  当CMS停止工作时,会把 Serial Old GC作为备选方案,而它是JVM中性能最差的垃圾收集方式,停顿几秒甚至十秒都有可能

  移除了CMS垃圾收集器,如果继续在JDK14中使用-XX:+UseConcMarkSweepGC 不会报错,仅仅给出一个warning警告

warning: Ignoring option UseConcMarkSweepGC; support was removed in 14.0

其他垃圾收集器

  G1回收器hotSpot已经默认使用有几年了,我们还看到两个新的GC JAVA11中的ZGC和openJDK12中的Shenandoah,后两者主要特点是:低停顿时间

  Shenandoah非Oracle官方发布的,是OpenJDK于JAVA12发布的

收集器名称运行时间总停顿时间最大停顿时间平均停顿时间
Shenandoah387.602s320ms89.79ms53.01ms
G1312.052s11.7s1.24s450.12ms
CMS286.264s12.78s4.39s852.26ms
ParallelScavenge260.092s6.59s3.04s823.75ms

4.ZGC on macOS and Windows

  JAVA14之前,ZGC仅仅支持Linux

  基于一些开发部署和测试的需要,ZGC在JDK14中支持在macOS 和windows,因此许多桌面级应用可以从ZGC中受益,目前还是一个实验性版本,要想在macOS 和windows上使用

  ZGC与Shenandoah目标非常相似,都是在尽量减少吞吐量的情况下,实现对任意堆大小(TB级)都可以把垃圾收集器停顿时间限制在10毫秒以内的低延迟时间

  ZGC 收集器是一款基于Region内存布局的,暂时不设分代的,使用了读屏障,染色指针和内存多重映射等技术来实现并发的标记压缩算法,以低延迟为首要目标的一款垃圾收集器.现在想在macOS 和windows上使用ZGC, 方式如下

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

关于ZGC的一些测试数据

image.png

image.png

  ZGC目前还处于一个实验状态,但是性能非常亮眼,未来将在服务端,大内存,低延迟应用上作为首选的垃圾收集器

三、其他变化

1.友好的空指针异常提示

  NullpointerException是java开发中经常遇见的问题,在JDK14之前的版本中,空指针异常的提示信息就是简答的null,并不会告诉我们更加有用的信息,知识根据异常产生的日志来进行查找和处理,对于很长的引用来说,很难定位到具体是哪个对象为null.

演示适用情况

public class Test2 {
    public static void main(String[] args) {
        Person p =new Person();
        p.cat.eat();

    }
}

class Person{
    public Cat cat;
}
class Cat {
    public void eat(){

    }
}

  上面的代码在调用eat方法时就会出现空指针异常

image.png

  这种提示其实并不是很详细,我们可以在运行代码的时候,加上一段配置,用以展示比较友好的控制成提示信息

-XX:+ShowCodeDetailsInExceptionMessages

  输出的信息如下所示

image.png

其他适用情况

  但是对于更复杂的代码,不适用调用器就无法确定是哪个变量为空

a.b.c.d=100;

  仅仅使用文件名和行数,并不能精确的提示到底是哪个变量为null

访问多维数组也会发生类似的情况

a[j][j][k]=99;

  这里如果发生npe文件名和行号也是无法精确指出到底是哪一层的数组出现了空指针

再例如

a.i=b.j;

  这里出现了npe 如果仅凭文件名和行号,无法确定到底是a的问题还是b的问题

NPE也可能在方法调用中传递

x().y().i=99;

  这里如果出现了NPE,那么仅凭行号和文件名也是无法确定到底是x方法还是y方法的问题

接下来简单演示一个代码


public class Test2 {
    public static void main(String[] args) {
       A a =new A();
       B b=null;
       a.i=b.j;

    }
}

class A{
  int i;
}
class B{
  int j;
}

如果没有友好提示

image.png

如果有友好提示

image.png

这时就可以发现,是j的问题,那么其实就是b对象的问题

2.JAVA打包工具 JEP343(孵化阶段)

  该特征旨在创建一个用于打包独立java应用程序的工具.JAVA应用的打包和分发一直都是个老大的难题. 用户希望JAVA引用的安装和运行方式和其他应用有相似的体验. 比如,在windows上只需要双击文件就可以运行. JAVA平台本身没有提供实用的工具解决这个问题. 通常都依赖第三方的工具完成,这个JEP的目标就是创建一个简单的JAVa打包工具jpackage. 相对于第三方工具,jpackage只适用于比较简单的场景,不过对很多应用来说已经足够好了.

  该jpackage工具将java的应用程序打包到特定的平台的程序包中,该程序包包含所必须的依赖. 该应用程序可以作为普通的jar文件或者模块的集合提供,受支持的特定平台的软件包格式为:

1 Linux deb或者 rpm

2 maxOS: pkg和dmg

3 windows L msi和exe

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

项目打包-非模块化项目

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

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

  将以本地系统的默认格式打包应用程序,将生成的打包文件保留到当前目录中. 如果MANIFEST.MF文件中没有main.jar.没有Main-Class属性,则必须显式指定主类

$ jpackage --name myapp --input lib --main-jar main.jar \ --main-class myapp.Main

  软件包的名称将为没有app ,尽管软件包文件本身的名称将更长,并以软件包类型皆为,该软件包将包括该应用程序的启动器,也称为myapp .要启动应用程序,启动程序将会从输入目录复制的每个jar文件放在jvm的类路径上

  如果您希望默认格式以外的其他格式制作软件包,请使用 --type选项. 例如,要在macOS 上生成pkg文件而不是dmg文件

$ jpackage --name myapp --input lib --main-jar main.jar --type pkg

项目打包-模块化项目

  如果您有一个模块化应用程序,该程序有目录中的模块化jar文件或JMOD文件组成,并且模块中lib包含主类myAPP,则命令为

$ jpackage -name myapp --moudule-path lib -m myapp

如果myAPP模块未标识主类,则必须再次明确

$ jpackage -name myapp --moudule-path lib -m myapp/myapp.Main

3.JFR事件流

简介

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

  JAVA14之前只能做离线的分析,现在可以做实时的持续监视

  它可以收集有关JVM以及在其上运行的Java应用程序的数据。JFR是集成到JVM中的,所以JFR对JVM的性能影响非常小,我们可以放心的使用它。

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

  JFR的历史很久远了。早在Oracle2008年收购BEA的时候就有了。JFR一般和JMC(Java Mission Control)协同工作。

  JFR是一个基于事件的低开销的分析引擎,具有高性能的后端,可以以二进制格式编写事件,而JMC是一个GUI工具,用于检查JFR创建的数据文件。

  这些工具最早是在BEA的JRockit JVM中出现的,最后被移植到了Oracle JDK。最开始JFR是商用版本,但是在JDK11的时候,JFR和JMC完全开源了,这意味着我们在非商用的情况下也可以使用了。

  而在今天的JDK 14中,引入了一个新的JFR特性叫做JFR Event Streaming,我们将在本文中简要介绍。

  先介绍一下JFR和JMC。

JFR

  上面我们简单的介绍了一下JFR。JFR是JVM的调优工具,通过不停的收集JVM和java应用程序中的各种事件,从而为后续的JMC分析提供数据。

  Event是由三部分组成的:时间戳,事件名和数据。同时JFR也会处理三种类型的Event:持续一段时间的Event,立刻触发的Event和抽样的Event。

  为了保证性能的最新影响,在使用JFR的时候,请选择你需要的事件类型。

  JFR从JVM中搜集到Event之后,会将其写入一个小的thread-local缓存中,然后刷新到一个全局的内存缓存中,最后将缓存中的数据写到磁盘中去。

  或者你可以配置JFR不写到磁盘中去,但是这样缓存中只会保存部分events的信息。这也是为什么会有JDK14 JEP 349的原因。

  开启JFR有很多种方式,这里我们关注下面两种:

  1. 添加命令行参数
-XX:StartFlightRecording:<options>

启动命令行参数的格式如上所述。

  JFR可以获取超过一百种不同类型的元数据。如果要我们一个个来指定这些元数据,将会是一个非常大的功能。所以JDK已经为我们提供了两个默认的profile:default.jfc and profile.jfc。

  其中 default.jfc 是默认的记录等级,对JVM性能影响不大,适合普通的,大部分应用程序。而profile.jfc包含了更多的细节,对性能影响会更多一些。

  如果你不想使用默认的两个jfc文件,也可以按照你自己的需要来创建。

下面看一个更加完整的命令行参数:

-XX:StartFlightRecording:disk=true,filename=/tmp/customer.jfr,maxage=5h,settings=profile

上面的命令会创建一个最大age是5h的profile信息文件。

  1. 使用jcmd

命令行添加参数还是太麻烦了,如果我们想动态添加JFR,则可以使用jcmd命令。

jcmd <pid> JFR.start name=custProfile settings=default
jcmd <pid> JFR.dump filename=custProfile.jfr
jcmd <pid> JFR.stop

上面的命令在一个运行中的JVM中启动了JFR,并将统计结果dump到了文件中。

上面的custProfile.jfr是一个二进制文件,为了对其进行分析,我们需要和JFR配套的工具JMC。

JMC

JDK Mission Control 是一个用于对 Java 应用程序进行管理、监视、概要分析和故障排除的工具套件。

在JDK14中,JMC是独立于JDK单独发行的。我们可以下载之后进行安装。

我们先启动一个程序,用于做JFR的测试。

@Slf4j
public class ThreadTest {

    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(10);
        Runnable runnable= ()->{
            while(true){
                log.info(Thread.currentThread().getName());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    log.error(e.getMessage(),e);
                }
            }
        };

        for(int i=0; i<10; i++){
            executorService.submit(runnable);
        }
    }
}

很简单的一个程序,启动了10个线程,我们启动这个程序。

然后再去看看JMC的界面:

JMC非常强大,也有很多功能,具体的细节大家可以自己运行去体会。

JFR事件

JMC好用是好用,但是要一个一个的去监听JFR文件会很繁琐。接下来我们来介绍一下怎么采用写代码的方式来监听JFR事件。

如果我们想通过程序来获取“Class Loading Statistics"的信息,可以这样做。

上图的右侧是具体的信息,我们可以看到主要包含三个字段:开始时间,Loaded Class Count和 Unloaded Class Count。

我们的思路就是使用jdk.jfr.consumer.RecordingFile去读取生成的JFR文件,然后对文件中的数据进行解析。

相应代码如下:

@Slf4j
public class JFREvent {

    private static Predicate<RecordedEvent> testMaker(String s) {
        return e -> e.getEventType().getName().startsWith(s);
    }

    private static final Map<Predicate<RecordedEvent>,
            Function<RecordedEvent, Map<String, String>>> mappers =
            Map.of(testMaker("jdk.ClassLoadingStatistics"),
                    ev -> Map.of("start", ""+ ev.getStartTime(),
                            "Loaded Class Count",""+ ev.getLong("loadedClassCount"),
                            "Unloaded Class Count", ""+ ev.getLong("unloadedClassCount")
                    ));

    @Test
    public void readJFRFile() throws IOException {
        RecordingFile recordingFile = new RecordingFile(Paths.get("/Users/flydean/flight_recording_1401comflydeaneventstreamThreadTest.jfr"));
        while (recordingFile.hasMoreEvents()) {
            var event = recordingFile.readEvent();
            if (event != null) {
                var details = convertEvent(event);
                if (details == null) {
                    // details为空
                } else {
                    // 打印目标
                    log.info("{}",details);
                }
            }
        }
    }

    public Map<String, String> convertEvent(final RecordedEvent e) {
        for (var ent : mappers.entrySet()) {
            if (ent.getKey().test(e)) {
                return ent.getValue().apply(e);
            }
        }
        return null;
    }
}

注意,在convertEvent方法中,我们将从文件中读取的Event转换成了map对象。

在构建map时,我们先判断Event的名字是不是我们所需要的jdk.ClassLoadingStatistics,然后将Event中其他的字段进行转换。最后输出。

运行结果:

{start=2021-04-29T02:18:41.770618136Z, Loaded Class Count=2861, Unloaded Class Count=0}
...

可以看到输出结果和界面上面是一样的。

JFR事件流

讲了这么多,终于到我们今天要讲的内容了:JFR事件流。

上面的JFR事件中,我们需要去读取JFR文件,进行分析。但是文件是死的,人是活的,每次分析都需要先生成JFR文件简直是太复杂了。是个程序员都不能容忍。

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

    public static void main(String[] args) throws IOException, ParseException {
        //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();
        }
    }

看看上面的例子。我们通过Configuration.getConfiguration("default")获取到了默认的default配置。

然后通过构建了default的RecordingStream。通过onEvent方法,我们对相应的Event进行处理。

4.外部存储器API (孵化阶段)

  通过一个API,以允许java程序安全有效的访问JAVA堆之外的外部存储(堆以外的外部存储空间)

  目的:JEP 370旨在实现一种提供“通用性”,“安全性”和“确定性”的“外部存储器API”JEP还指出,此外部内存API旨在替代当前使用的方法( java.nio.ByteBuffersun.misc.Unsafe )。

  许多java的库都能访问外部存储,例如 ignite ,mapDB , memcached以及netty的ByteBuffer API ,这样可以:

  • 避免垃圾回收相关成本和不可预测性
  • 跨多个进程共享内存
  • 通过将文件映射到内存中来序列化和反序列化内容

但是JAVAAPI本身没有提供一个令人满意的访问外部内存的解决方案

当java程序需要访问堆内存之外的外部存储是,通常有两种方式

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

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

  JEP 370的“描述”部分引入了安全高效的API来访问外部外部内存地址,目前该API还是属于孵化阶段,相关API在jdk.incubator.foreign模块的jdk.incubator.foreign包中, 三个API分别是: MemorySegmentMemoryAddressMemoryLayoutMemorySegment用于对具有给定空间和时间范围的连续内存区域进行建模。 可以将 MemoryAddress视为段内的偏移量。 最后, MemoryLayout是内存段内容的程序化描述。

5.非易失性映射字节缓冲区

  JAVA14增加了一种文件映射模式,用于访问非易失性内存,非易失性内存能够持久保持数据,因此可以利用该特性来改进性能

  JEP352 可以使用FileChannelAPI创建引用非易失性内存,(non-volatile memory) 的MappedByteBuffer实例,该JEP建议升级MappedByteBuffer以支持对非易失性存储器的访问,唯一需要的API更改是FileChannel客户端,以请求映射位于NVM的支持的文件系统,而不是常规的文件存储系统上的文件,对MappedByteBuffer API最新的更改意味着他支持允许直接内存更新所需要的所有行为,并提供更高级别的JAVA客户端库所需要的持久性保证,以实现持久性的数据类型

目标

  NVM为引用程序程序员提供了在程序运行过程中创建和更新程序转台的机会,而减少了输出到持久性介质或者从持久性介质输入是的成本. 对于事务程序特别重要,在事务程序中,需要定期保持不确定状态以启用崩溃恢复.

  现有的C库(例如Intel的libpmen),为c程序员提供了对集成NVM的高效访问,它们还一次基础来支持对各种持久性数据类型的简单管理.当前,由于频繁需要进行系统调用或者JNI来调用原始操作,从而确保内存更改是持久的,因此即使禁用JAVA的基础类库也很昂贵.同样的问题限制了高级库的使用.并且,由于C中提供的持久数据类型分配在无法从JAVA直接访问的内存中这一事实而加剧了这一问题.

  该特性试图通过允许映射到ByteBuffer的NVM的有效写回来解决第一个问题. 由于java可以直接访问ByteBuffer映射内存,因此可以通过实现与C语言中提供的客户端库等效的客户端库来解决第二个问题,以管理不同持久数据类型存储.

初步变更

该JEP使用了JAVASE API的两个增强功能

  • 支持 Implementation-defined的映射模式
  • MappedByteBuffer::force方法指定范围

特定于JDK的API更改

  • 通过新模块中的公共API公开新的MApMode枚举值

一个公共扩展枚举ExtendedMapMode将添加到jdk.nio.mapmode程序包

package jdk.nio.mapmode;
public class ExtendedMapMode{
    private ExtendedMapMode(){
  
    }
    public static final MapMode READ_ONLY_SYNC=  ... ...
  
}

  在调用FileChannel::map方法创建映射到NVM设备文件上的只读或者写MappedByteBuffer时,可以使用上述的枚举值,如果这些标志在不支持NVM设备文件平台上传递,程序会抛出UnsupportedOperationException异常,在受支持的平台上,及当目标FileChannel实例是通过NVM设备打开的派生文件是,才能传递这些参数,在任何情况下,都会抛出IOException;

JDK15特性

一、JAVA15概述

2020年9月15日,java15正式发布,(风平浪静的一个版本)共有14个JEP,是时间驱动形式发布的第六个版本.相关文档: openjdk.java.net/projects/jd…

image.png

image.png

二、语法层面的变化

1.密封类(预览)

JEP360:Sealed Classes(Preview)密封的类和接口预览

通过密封的类和接口来增强Java编程语言,这是新的预览特性,用于限制超类的使用密封的类和接口限制其他可继承或者实现他们的其他类或接口.

目标

允许类或接口的开发者来控制那些代码负责实现,提供了比限制使用超类的访问修饰符声明方式更多选择,并通过支持对模式的详尽分析而支持模式匹配的未来发展

在java中,类层次构造通过集成实现代码的重用,父类的方法可以被许多子类继承.但是,类层次接口的目的并不总是重用代码.有时是对域中存在的各种可能性进行建模,例如图形库支持函的形状类型.当以这种方式使用类层次结构是,我们可能需要限制子类集从而简化建模.

虽然我们可以通过final来限定子类继承,但是这是绝对杜绝类子类,而类的密封是允许子类,但是限定是那个或者哪些.

具体方式

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

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

示例代码

public class TestSealedClass {
}
/*sealed 对Person类进行密封
* permits 指明哪些类可以继承
* 子类必须是final修饰的或者也是密封的
* 如果子类不想被密封,可以使用non-sealed修饰
* */
sealed class Person permits Worker,Teacher,Cook,Boss,Employee,Student {}
final class Cook              extends Person{}
final class Boss              extends Person{}
final class Employee          extends Person{}
final class Teacher           extends Person{}
// 密封的子类允许继续有子类
sealed class Student          extends Person permits PrimaryStudent,GraduateStudent{}
final class PrimaryStudent    extends Student{}
final class GraduateStudent   extends Student{}
// 通过non-sealed取消子类密封
non-sealed class Worker       extends Person{}
class CarWorker               extends Worker{}

密封接口 指定实现类的接口

public class Test2 {
}

/*
* 只有接口可以继承接口
* 一个接口可以同时继承多个接口
* final不能修饰接口,密封接口在被继承时,子接口要么使用 sealed non sealed  修饰
* */
sealed interface  Myinter1 permits Myinter3{}
sealed interface  Myinter2 permits Myinter3 {}
sealed interface  Myinter3 extends Myinter1,Myinter2{}
non-sealed class MyImpl implements Myinter3{}

sealed interface I permits A,B,C  {}
final class A implements I{}
sealed class B implements I{}
non-sealed class C implements I{}

final class D extends B{}

密封接口不可以使用匿名内部类进行实现

密封列与接口和模式匹配问题

public class TestSealedClass {
    public static void main(String[] args) {
        test(new C());
    }
    public static void test(C c){
        if( c instanceof I){
            System.out.println( "it is an i");
        }else{
            System.out.println("it is not i");
        }
    }

}
interface I{

}
sealed class C implements I permits D,E{}
non-sealed class D extends C{}
final class E extends C {}
// 密封类仅仅是控制类的继承和实现关系,不会影响我们的模式匹配

密封接口和records

record是隐匿式的final,可以直接实现密封接口

package com.msb.test2;

public class TestRecords {
    public static void main(String[] args) {
        MyInter1 myInter1=new Person(10,"旋涡刘能");
   
    }
}

sealed interface  MyInter1{
    public void eat();
}

/*record 默认继承的 java.lang.Record
* record可以直接实现密封接口,不需要用sealed 修饰 non-sealed 修饰
* record本身是隐式的final修饰
* 
* */

record Person(Integer pid,String pname)  implements MyInter1 {
    @Override
    public void eat() {
  
    }
}
record Student(Integer pid,String pname) implements MyInter1{
    @Override
    public void eat() {
  
    }
}
record Cook(Integer pid,String pname) implements MyInter1{
    @Override
    public void eat() {
  
    }
}
record Worker(Integer pid,String pname) implements MyInter1{
    @Override
    public void eat() {
  
    }
}

2.隐藏类

JEP371 :HiddenClass(隐藏类)

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

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

什么是Hidden Class

就是不能直接被其他class的二进制代码使用的class. 主要被一些框架用来生成运行时类,但是这些类不能被用来直接使用的,是通过反射来调用的

比如JDK8中引入的lambda表达式,编译时不会将lambda表达式转换为专门的类,而是在运行时将相应的字节码动态生成相应的类对象

另外使用动态代理也可以为某些类生成新的动态类

特征

我们希望这样的动态类有哪些特征呢?

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

API支持

因此,我们需要一些API来定义无法发现的且具有有限声明周期的隐藏类,这将有助于提高基于JVM的语言实现效率.比如

java.lang.reflect.Proxy 可以定义隐藏类作为实现代理接口的代理类

java.lang.invoke.StringConcatFactory可以生成隐藏类来保存常量连接方法

java.lang.invoke.LambdaMetaFactory可以生成隐藏的nestmate类,以容纳访问封闭变量的lambda主体

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

3.instanceof模式匹配(预览)

JAVA 14中作为预览语言功能引入instanceof模式匹配,在JAVA15中出于第二次预览,而没有任何更改,回顾JAVA14即可

4.Records(预览)

Records Class 第二次预览

JDK14中引入了Records, 只用一个Records可以很方便的创建一个常量类,就是一个数据的透明持有类,简化专门用于存储数据的类的创建语法

当声明一个Record时,该类将自动获取的内容

  • 获取成员变量的简单方法, 就是get方法,get方法将简化为成员变量同名方法
  • 一个equals的实现
  • 一个hashcode的实现
  • 一个toString的重现
  • 一个全参构造方法
  • 对应声明的所有final修饰的成员变量

5.文本块(确定)

JAVA13开始引入文本块,JAVA14 进行二次预览,JAVA15中成为一个正式的标准,参照JAVA14中的文本块回顾即可

三 关于虚拟机

1.ZGC功能(确定)

JEP377:ZGC :A Scalable Low_Latency Garbage Collector ZGC 功能成为正式标准

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

相关的参数有:

ZAllocationSpikeTolerance ZCollectionInterval ZFragmentationLimit ZMarkStackSpaceLimit ZProcative ZUncommit ZunCommitDelay ZGC-specific JFR events(ZAllocationStall ZPageAllocation ZPageCacheFlush ZRelocationSet ZRelocationSetGroup Zuncommit ) 也从experimental变为product

2.ShenandoahGC垃圾收集算法转正

Shenandoah垃圾回收算法终于从实验特性转变为产品特性

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

Shenandoah 和ZGC 对比

  • 相同: 性能几乎认为是相同的
  • 不同: ZGC是OracleJDK的, 而Shenandoah只存在于OpenJDK中,因此使用时需要注意JDK版本

打开方式: 使用-XX:+UseShenandoahGC命令行参数打开

Shenandoah在JDK12作为experimental引入,在JDK15变为Production ,之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC ,现在只需要-XX:+UseShenandoahGC

四 其他变化

1.edDSA签名算法

Edwards-Curve Digital Singnature Algorithm 数字曲线签名算法

这是一个新功能,新加基于EdWardS-Curve 数字签名算法,与JDK中现有的签名方案相比,EdDSA具有更高的安全性和性能,因此备受关注.它已经在OpenSSL和BoringSSL等加密库中得到支持,在区块链领域用的比较多.

EdDSA是一种现代椭圆曲线方案,具有JDK中现有签名方案的优点,EdDSA将只在SunECMA提供中实现

2.禁用偏向锁定

jep 374 Disable and Deprecate Biased Locking 禁用偏向锁定

在默认情况下禁用偏向锁定,并弃用所有的相关命令选项.目标是确定是否需要继续支持偏置锁定的高维护成本的遗留同步优化.HotSpot虚拟机使用该优化来减少非竞争锁的开销. 尽管某些JAVA应用程序在禁用偏向锁后可能会出现性能下降,但是偏向锁的性能提高通常不像以前那么明显

该特性默认禁用了 biased locking(-XX:+UseBisaedLocking),并且废弃了所有相关的命令行选型(BiasedLockingStartupDelay,BiasedLockingBulkRebiasThreshold,BiasedLockingBulkRevokeThreshold,BiasedLockingDecayTime,UseOptoBiasInlining,PrintBisasedLockingStatistics and PrintPreciseBiasedLockingStatistics)

3.重新实现SocketAPI

JEP373 Reimplement the legcy DatagramSocketAPI 重新实现DatagramSocketAPI

作为JEP353的后续,该方案重新实现了遗留的套接字API. java.net.datagram.Socket 和java.netMulticastSocket的当前实现可以追溯到JDK1.0,当时IPV6还在开发中. 因此,当前的套接字实现尝试调和IPV4和IPV6难以维护的方式.

具体情况

  • 通过替换 java.net.datagram 的基础实现,重新实现旧版DatagramSocket API
  • 更改java.net.DatagramSocket和java.net.MulticastSocket 为更加简单,现代化的底层实现,提高了JDK的可维护性和稳定性

新的实现

1 易于维护和调试

2 Project Loom中正在探索虚拟线程协同

4.外部存储API

JEP383 Foreign-Memory Access API (Second Incubator) 外部存储器访问API

目的是引入一个API.以允许java程序安全.有效地访问JAVA对之外的外部存储器.如本机,持久和托管堆.

有许多JAVA程序访是访问外部内存的,比如 Ignite和MapDB.该API将有助于避免与垃圾收集相关的成本以及与跨进程共享内存以及通过将文件映射到内存来序列化和返序列化内存内容相关的不可预测性. 该java API 目前没有为访问外部内存停工令人满意的解决方案.但是在新的提议中,API不应该破坏JVM的安全性

Foreign-Memory Access API在JDK14中作为 incubating API引入,在JDK15中出于 Second Incubator,提供了改进.

5.废弃和移除

废弃

  • Deprecated RMI Activation For Removal

RMI Activation(延迟激活: 延迟激活对象,推迟到客户第一次使用之前)被标记为删除,在未来的版本中将会删除,自JAVA8依赖一直是可选的,而不是必选项目. RMI激活机制增加了持续的维护负担,RMI的其他部分暂时不会被弃用.

对于现在应用程序来说. 分布式系统大部都是基于Web的,web服务器已经解决了穿越防火墙,过滤请求,身份验证和安全性问题,并且也提供了很多延迟加载的技术.所以在现代引用程序中,RMIActivation已经很少用了,并且在各种开源代码库中,也基本上找不见了

在JDK8中, RMI Activation被置为可选,JDK15 中,废弃了

  • Deprecated -XX:ForceMUMA Option ,废弃了ForceNUMA选项
  • Disable Native SunEC Implementation by Default 默认禁用了Native SunEC Implementation

移除

  • Obsolete -XX:UseAdaptiveGCBoundary,淘汰了 -XX:UseAdativeGCBoundary
  • 移除Solaris和SPCRC端口

近年来,Solaris和SPARC都已被Linux操作系统和英特尔处理器取代.放弃对Solaris和SPARC 端口的支持,将使OpenJDK社区的贡献者们能够加速开发新功能,从而推动平台向前发展

  • 移除 Nashorn JS 引擎

Nashorn 是JDK提出的脚本执行引擎,该功能时2014年3月发布的JDK8的新特性,在JDK11就已经把它标记为废弃了,JDK15完全移除了

在JDK中取以代之的是GraalVM . GraalVM 是一个运行时平台,他支持java和其他基于java字节码的语言,但也支持其他语言,如JAVAScript Ruby Python 或者 LLVM. 性能是Nashorn 的两倍以上

JDK15 移除了Nashorn JAVAScript Engine 以及jjs命令工具,具体就是jdk.scripting.nashorn及jdk.scripting.nashorn.shell这两个模块移除了

image.png

Graal VM在hotSpot VM基础上,增强而形成的跨语言全栈虚拟机,可以作为"任何语言"的运行平台使用.