JDK8以后新特性

内容分享3周前发布
1 1 0

JDK10新特性

JDK10新特性介绍

  • 局部变量类型推断
  • 将 JDK 的多个代码仓库合并到一个储存库中
  • 垃圾收集器接口
  • G1 引入并行 Full GC
  • 应用程序类数据共享
  • 线程-局部变量管控
  • 删除JNI本地头文件生成工具(javah)
  • 额外的 Unicode 语言标签扩展
  • 在备用存储装置上的堆分配
  • 基于Java的实验性JIT编译器
  • 根证书
  • 基于时间的发行版本控制
  • 新增API

JDK10新特性1-局部变量类型推断

JDK10之前定义变量存在的问题

许多人抱怨Java是一种强类型,需要引入大量的样板代码。很明显类型声明往往被认为是不必要的。JDK10之前的Java代码中,声明一个变量是超级繁琐的:

String str = "abc";
long l = 10L;
boolean b = true;
ArrayList<String> list = new ArrayList();
Stream<String> stream = list.stream();

许多流行的编程语言都已经支持某种形式的局部变量类型推断,如JS(var)。

JDK10局部变量类型推断的使用

JDK10 可以使用 var 进行 局部变量类型推断

var str = "abc"// 推断为 字符串类型
var l = 10L; // 推断为long 类型
var flag = true// 推断为 boolean 类型
var list = new ArrayList<String>();  // 推断为 ArrayList<String>
var stream = list.stream(); // 推断为 Stream<String>

局部变量类型推断使用场景

  1. 局部变量
  2. 循环内public class demo01var {
    // var x = 10; // 成员变量不能使用var
    public static void main(String[] args) {
    var a = 1;
    var str = “abc”;
    var flag = true;
    var list = new ArrayList<String>();
    list.add(“aa”);
    var stream = list.stream();

    for (var s : list) {
    System.out.println(s);
    }

    for (var i = 0; i < 10; i++) {
    var x = 5;
    }
    }
    }

局部变量类型推断不能使用场景

  1. 成员变量
  2. 方法参数
  3. 方法返回类型public class demo01var {
    // var x = 10; // 成员变量不能使用var
    public static void main(String[] args) {
    }

    // 参数不能使用var
    // public static void test01(var a) {}

    /* 方法返回类型不能使用var
    public static var test02() {
    return true;
    }*/
    }

局部变量类型推断注意事项

  1. var 并不是一个关键字,可以作为标识符,这意味着可以将一个变量、方法、包名写成 var。不过一般情况下不会有人这么写的,由于这本身就违反了普遍的命名规范。// var 并不是一个关键字,而是一个保留的类型名称,这意味着可以将一个变量、方法、包名写成 `var`。
    public static void test03() {
    var var = 10;
    System.out.println(“var = “ + var);
    }
  2. var声明变量的时候必须赋值、不能用于声明多个变量的情况。// var 不能用来声明没有赋值的变量、不能用于声明多个变量的情况。
    public static void test04() {
    // var x = null; // 不行,推断不出到底是什么类型
    int x = 1, y = 2; // 可以
    // var m = 1, n = 2; // 不行
    }

小结

我们了解了JDK10之前定义变量是比较繁琐的,通过JDK10的局部变量类型推断定义变量更加的简单。

如何进行局部变量类型推断?

将变量名左边的类型替换成var即可
var i = 3;

什么情况下可以使用局部变量类型推断?

  1. 局部变量
  2. 循环中的变量

JDK10新特性2-将 JDK 的多个代码仓库合并到一个储存库中

了解这个新特性

它是一项对JDK源码进行管理的方案,开发人员无法直接使用

该新特性的动机

多年以来,JDK的完整代码库已分解为许多的仓库中。在 JDK9 中,JDK的源码被分成有 8 个仓库:root、corba、hotspot、jaxp、jaxws、jdk、langtools 和 nashorn 。JDK的源码分成多个仓库这种情况在支持各种所需的源代码管理操作方面做得很差。特别是,不可能在相互依赖的变更集的存储库之间执行原子提交。例如,如果今天用于单个错误修复或RFE的代码跨越了jdk和hotspot仓库,则无法在托管这两个不同仓库的目录林中原子地对两个仓库进行更改。跨越多个存储库的更改是很常见的。

将 JDK 的多个代码仓库合并到一个储存库中的好处

在 JDK10 中这些将被合并为一个,使得跨相互依赖的变更集的存储库运行 atomic commit (原子提交)成为可能。

JDK10新特性3-垃圾回收器接口

这不是让开发者用来控制垃圾回收的接口,而是方便JDK的开发人员在JVM 源代码中快速的集成和移除垃圾回收器。

该新特性的动机

当前,垃圾回收器的代码分散,不方便新增新的垃圾回收器也不利于移除现有垃圾回收器。

通过引入一个干净的垃圾回收器(GC)接口来改善不同垃圾回收器的源代码隔离。

该新特性的目标

  • JVM内部GC代码的更好的模块化
  • 使在不影响当前代码库的情况下向JVM添加新GC变得更加简单
  • 使从JVM构建中排除GC更容易

<img src="https://img.niaorui.com/blogimg/20251031/7fae5d8f1cc64ec29ffbb09183d86da8.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

JDK10新特性4-G1 引入并行 Full GC

该新特性的动机

G1 是设计来作为一种低延时的垃圾回收器。G1收集器还可以进行超级准确地对停顿进行控制。从JDK7开始启用G1垃圾回收器,在JDK9中G1成为默认垃圾回收策略。截止到ava 9,G1的Full GC采用的是单线程算法。也就是说G1在发生Full GC时会严重影响性能。

该新特性的效果

JDK10又对G1进行了提升,G1 引入并行 Full GC算法,在发生Full GC时可以使用多个线程进行并行回收。能为用户提供更好的体验。

JDK10新特性5-应用程序类数据共享

它是一项JVM性能增强功能,开发人员无法直接使用

该新特性的动机

<img src="https://img.niaorui.com/blogimg/20251031/6de32c94fc55459587bfa9fa8b6ea533.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

<img src="https://img.niaorui.com/blogimg/20251031/fc43a5881c29451ab163f7cd5884aa84.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

<img src="https://img.niaorui.com/blogimg/20251031/b6ee80588ea147cd9413813038ecc16c.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

JDK 5中引入的类数据共享,将一组类预处理为共享的存档文件,然后可以在运行时对其进行内存映射以减少启动时间。当多个JVM共享同一个存档文件时,它还可以减少动态内存占用。

JDK 5仅允许引导类加载器加载归档的类。JDK10对应用程序类数据共享进行了扩展,允许“应用程序类加载器”,内置平台类加载器和自定义类加载器加载已归档的类。

该新特性的目标

  • 通过在不同的Java进程之间共享通用的类元数据来减少占用空间。
  • 缩短程序启动时间。

JDK10新特性6-线程本地握手

该新特性的动机

Safepoint是Hotspot JVM中一种让应用程序所有线程停止的机制。为了要做一些超级之安全的事情,需要让所有线程都停下来它才好做。列如菜市场,人来人往,有人忽然要清点人数,这时候,最好就是大家都原地不动,这样也好统计。Safepoint起到的就是这样一个作用。

JVM会设置一个全局的状态值。应用线程去观察这个值,一旦发现JVM把此值设置为了“大家都停下来”。此时每个应用线程就会得到这个通知,然后各自选择一个point(点)停了下来。这个点就叫Safepoint。待所有的应用线程都停下来了。JVM就开始做一些安全级别超级高的事情了。

列如下面这些事情:

垃圾清理暂停。类的热更新。偏向锁的撤销。各种debug操作。

不过,让所有线程都到就近的safepoint停下来本是需要较长的时间。而且让所有线程都停下来显得有些粗暴。

为此Java10就引入了一种可以不用stop all threads的方式,就是Thread Local Handshake(线程本地握手)。

该新特性的效果

线程本地握手是在 JVM 内部相当低级别的更改,修改安全点机制,允许在不运行全局虚拟机安全点的情况下实现线程回调,使得部分回调操作只需要停掉单个线程,而不是停止所有线程。

JDK10新特性7-在备用存储装置上进行堆内存分配

该新特性的动机

<img src="https://img.niaorui.com/blogimg/20251031/e4b6ce3e5e5643789d2eadde5b72a6c1.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

NVDIMM-非易失性双列直插式内存模块(英语:non-volatile dual in-line memory module,缩写NVDIMM)特点:价格便宜,速度比DRAM慢,断电能保留数据。

随着廉价的NV-DIMM内存的可用性,未来的系统可能会配备异构内存架构。除了DRAM之外,这种架构还将具有一种或多种具有不同特性的非DRAM存储器。

<img src="https://img.niaorui.com/blogimg/20251031/039a3dd4209f4215b3be783bb9d41cf4.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

该JEP的目标是无需更改现有的应用程序代码可以取代DRAM用于对象堆。所有其他内存结构(例如代码堆,元空间,线程堆栈等)将继续驻留在DRAM中。

应用场景

  1. 在多JVM部署中,某些JVM(例如守护程序,服务等)的优先级低于其他JVM。与DRAM相比,NV-DIMM具有更高的访问延迟。低优先级进程可以将NV-DIMM内存用于堆,从而允许高优先级进程使用更多的DRAM。
  2. 大数据和内存数据库等应用程序对内存的需求不断增长。这样的应用程序可以将NV-DIMM用于堆,由于与DRAM相比,NV-DIMM可能具有更大的容量,且成本更低。

JDK10新特性8-基于Java的实验性JIT编译器

该新特性的动机

Java编译器指的是JDK自带的javac指令。这一指令可将Java源程序编译成.class字节码文件(bytecode)。字节码无法直接运行,但可以被不同平台JVM中的 interpreter(解释器) 解释执行。由于一个Java指令可能被转译成十几或几十个对等的微处理器指令,这种模式执行的速度相当缓慢。

<img src="https://img.niaorui.com/blogimg/20251031/94046f420ada4abebe0d75071fb777ba.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

该新特性的目的

由于interpreter效率低下,JVM中又增加JIT compiler(即时编译器,just in time)会在运行时有选择性地将运行次数较多的方法编译成二进制代码,直接运行在底层硬件上。花费少许的编译时间来节省稍后相当长的执行时间,JIT这种设计的确增加不少效率,但是它并未达到最顶尖的效能,由于某些极少执行到的Java指令在JIT编译时所额外花费的时间可能比interpreter解释器执行时的时间还长,针对这些指令而言,整体花费的时间并没有减少。

Graal是基于Java的JIT编译器,这项 JEP 将 Graal 编译器研究项目引入到 JDK 中。为了让 JVM 性能与当前 C++ 所写版本匹敌(或有幸超越)提供基础。

JDK10新特性9-删除javah工具

javah 用于生成C语言的头文件。

该新特性的动机

从JDK8开始,javah的功能已经集成到了javac中。去掉javah工具。

javac -h . 文件名.java

JDK10新特性10-额外的 Unicode 语言标签扩展

该新特性的动机

之前对Unicode语言环境扩展仅限于日历和数字。该JEP在相关JDK类中实现最新规范中指定的更多扩展。

对以下附加扩展的支持:

  • cu (货币类型)
  • fw (一周的第一天)
  • rg (区域覆盖)
  • tz (时区)

为了支持这些附加扩展,将对以下API进行更改:

  • java.text.DateFormat::getInstance将根据扩展名返回实例carg和/或tz
  • java.text.DateFormatSymbols::getInstance 将根据扩展名返回实例 rg
  • java.text.DecimalFormatSymbols::getInstance 将根据扩展名返回实例 rg
  • java.text.NumberFormat::get*Instance将根据扩展名nu和/或返回实例rg
  • java.time.format.DateTimeFormatter::localizedBy将返回DateTimeFormatter基于扩展情况下carg和/或tz
  • java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern将根据rg扩展名返回模式字符串。
  • java.time.format.DecimalStyle::ofDecimalStyle根据扩展名返回实例nu,和/或rg
  • java.time.temporal.WeekFields::ofWeekFields根据扩展名fw和/或返回实例rg
  • java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}将根据扩展名fw和/或返回值rg
  • java.util.Currency::getInstanceCurrency根据扩展名cu和/或返回实例rg
  • java.util.Locale::getDisplayName 将返回一个字符串,其中包括这些U扩展名的显示名称
  • java.util.spi.LocaleNameProvider 这些U扩展的键和类型将具有新的SPI

JDK10新特性11-根证书

该新特性的动机

Open JDK源代码中的密钥库当前为空。因此,默认情况下,TLS等关键安全组件在Open JDK构建中不起作用。

JDK10开源Oracle Java SE Root CA程序中的根证书,减少这些构建与Oracle JDK构建之间的差异。

JDK10新特性12-基于时间的发行版本控制

该新特性的动机

从JDK9后来每六个月内严格发布Java SE平台和JDK的新版本 。之前的命名方案不太适合将来。

重铸JEP 223引入的版本号方案,以使其更适合于基于时间的发布模型,这些模型定义功能发布(可以包含新功能)和更新发布(仅修复错误)。

使开发人员或最终用户易于确定版本的发布时间,以便他们可以判断是否将其升级到具有最新安全修复程序以及可能的附加功能的新版本。

JDK10新增API

  1. 集合新增的copyof方法
  2. Reader新增transferTo方法
  3. IO流大家族添加Charset参数的方法
  4. ByteArrayOutputStream新增toString方法

步骤

  1. List、Set、Map新增加了一个静态方法copyOf方法
  2. 对copyOf返回的集合进行修改
  3. 查看copyOf源码

JDK10 给 java.util 包下的List、Set、Map新增加了一个静态方法copyOf。copyof方法将元素放到一个不可修改的集合并返回。

代码

public class Demo02 {
   public static void main(String[] args) {
       var list = new ArrayList<String>();
       list.add("aa");
       list.add("bb");
       list.add("cc");
       list.add("dd");

       var list2 = List.copyOf(list);
       System.out.println(list2);
       System.out.println(list2.getClass());
       list2.add("ee");
  }
}

运行出错

<img src="https://img.niaorui.com/blogimg/20251031/3bdbb6f0b8a647e8a0d0ed8f9dcda519.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

不能往copyof返回的集合中添加元素,由于返回的是不可变的集合:class
java.util.ImmutableCollections$ListN

查看
java.util.ImmutableCollections$ListN源码:

static final class ListN<Eextends AbstractImmutableList<Eimplements Serializable {
...
}

可以看到ListN继承了AbstractImmutableList。再看AbstractImmutableList的源码:

static abstract class AbstractImmutableList<Eextends AbstractImmutableCollection<E>
           implements List<E>, RandomAccess {

// all mutating methods throw UnsupportedOperationException
@Override public void    add(int index, E element) throw uoe(); }
@Override public boolean addAll(int index, Collection<? extends E> c) throw uoe(); }
@Override public E       remove(int index) throw uoe(); }
@Override public void    replaceAll(UnaryOperator<E> operator) throw uoe(); }
@Override public E       set(int index, E element) throw uoe(); }
@Override public void    sort(Comparator<? super E> c) throw uoe(); }
}

可以看到在AbstractImmutableList中所有对元素增删改的方法都会抛出异常。由此可见copyof返回的是不可变集合。

List,Set,Map的copyof方法代码:

public class Demo02 {
   public static void main(String[] args) {
       var list = new ArrayList<String>();
       list.add("aa");
       list.add("bb");
       list.add("cc");
       list.add("dd");

       var list2 = List.copyOf(list);
       System.out.println(list2);
       System.out.println(list2.getClass());
       list2.add("ee");

       System.out.println("---------------");
       var set = new HashSet<String>();
       set.add("b");
       set.add("a");
       set.add("c");
       set.add("d");

       var set2 = Set.copyOf(set);
       System.out.println(set2.getClass());
for (String string : set2) {
System.out.println(string);
}

       System.out.println("---------------");

       var map = new HashMap<>();
       map.put("k1""v1");
       map.put("k2""v2");

       var map2 = Map.copyOf(map);
       System.out.println(map2.getClass());
       var keys = map2.keySet();
       for (Object object : keys) {
           System.out.println(map2.get(object));
      }
  }
}

小结

List,Set,Map新增的copyOf方法,返回的是一个新的集合,这个新的集合是不可变的集合,不能改变集合中的内容

Reader新增transferTo方法

步骤

  1. 以前IO流复制文件
  2. JDK10使用transferTo方法复制文件

以前IO流复制文件

private static void test01() throws IOException {
   // 字符流复制文本文件
   FileReader fis = new FileReader("JDK10\files\a.txt");
   FileWriter fos = new FileWriter("JDK10\files\b.txt");
   char[] chs = new char[1024 * 8];
   int len;
   while ((len = fis.read(chs)) != -1) {
       fos.write(chs, 0, len);
  }

   fis.close();
   fos.close();
}

以上代码要自己定义数组,编写循环读取和写数据的代码,比较麻烦。使用JDK10的 transferTo 方法就很简单了。

JDK10使用transferTo方法复制文件

JDK10 给 InputStream 和 Reader 类中新增了 transferTo 方法,transferTo 方法的作用是将输入流读取的数据使用字符输出流写出。可用于复制文件等操作。

private static void test02() throws IOException {
   FileReader fis = new FileReader("JDK10\files\a.txt");
   FileWriter fos = new FileWriter("JDK10\files\c.txt");
   fis.transferTo(fos);

   fis.close();
   fos.close();
}

我们只需要调用 transferTo 方法就可以复制文件了。我们来看看 Reader 类的 transferTo 源码:

public long transferTo(Writer out) throws IOException {
   Objects.requireNonNull(out"out");
   long transferred = 0;
   char[] buffer = new char[TRANSFER_BUFFER_SIZE];
   int nRead;
   while ((nRead = read(buffer, 0, TRANSFER_BUFFER_SIZE)) >= 0) {
       out.write(buffer, 0, nRead);
       transferred += nRead;
  }
   return transferred;
}

小结

JDK10中给输入流InputStream&Reader新增了transferTo,可以将输入流中的数据转到输出流中.方便文件的复制

IO流大家族添加Charset参数的方法

IO流大家族添加Charset参数的方法

<img src="https://img.niaorui.com/blogimg/20251031/bef29249bb2044eebd11062c8da8ca7f.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

PrintStream,PrintWriter,Scanner添加了带Charset参数的构造方法,通过Charset可以指定IO流操作文本时的编码。

public class Demo04 {
   public static void main(String[] args) throws IOException {
       test01();
       test02();
  }

   // 使用指定的GBK编码打印数据
   private static void test02() throws IOException {
       PrintStream ps = new PrintStream("JDK10\files\ps2.txt",Charset.forName("GBK"));
       ps.println("你好");
       ps.close();
  }

   // 使用IDEA默认的UTF-8编码打印数据
   private static void test01() throws FileNotFoundException {
       PrintStream ps = new PrintStream("JDK10\files\ps1.txt");
       ps.println("你好");
       ps.close();
  }
}

小结

在JDK10中给IO流中的许多类都添加了带Charset参数的方法,列如PrintStream,PrintWriter,Scanner。通过Charset可以指定编码来操作文本。Charset是一个抽象类,我们不能直接创建对象,需要使用Charset的静态方法forName(“编码”),返回Charset的子类实例。

ByteArrayOutputStream新增toString方法

<img src="https://img.niaorui.com/blogimg/20251031/87b95fbf9167481ab2b51cb6ba7072d1.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

JDK10给ByteArrayOutputStream新增重载toString(Charset charset)方法,通过指定的字符集编码字节,将缓冲区的内容转换为字符串。

public class Demo06 {
   public static void main(String[] args) throws UnsupportedEncodingException {
       String str = "你好中国";

       ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes("GBK"));
       ByteArrayOutputStream bos = new ByteArrayOutputStream();
       int b;
       while ((b = bis.read()) != -1) {
           bos.write(b);
      }

       System.out.println(bos.toString());
       System.out.println(bos.toString("GBK"));
  }
}

小结

通过ByteArrayOutputStream新增的toString(Charset), 可以将字节数组输出流中的数据按照指定的编码转成字符串

JDK11新特性

基于嵌套的访问控制

如果你在一个类中嵌套了多个类,各类中可以直接访问彼此的私有成员。由于JDK 11开始在private,public,protected的基础上,JVM又提供了一种新的访问机制:Nest。

我们先来看下JDK 11之前的如下案例:

class Outer {
   private int outerInt;

   class Inner {
       public void test() {
           System.out.println("Outer int = " + outerInt);
      }
  }
}

在JDK 11之前 执行编译的最终结果的class文件形式如下:

class Outer {
   private int outerInt;

   public int access$000() {
       return outerInt;
  }
}

class Inner$Outer {
   Outer outer;

   public void test() {
       System.out.println("Outer int = " + outer.access$000());
  }
}

以上方案虽然从逻辑上讲,内部类是与外部类一样的代码实体的一部分,但它被编译为一个单独的类。因此,它需要编译器创建合成桥接方法,以提供对外部类的私有字段的访问。这种方案一个很大的坑是反射的时候会有问题。当使用反射在内部类中访问外部类的私有成员outerInt时会报IllegalAccessError错误。这个是让人不能理解的,由于反射还是源码级访问的权限。

class Outer {
   private int outerInt;

   class Inner {
       public void test() throws Exception {
           System.out.println("Outer int = " + outerInt);
           // JDK 11之前,如下代码报出异常:IllegalAccessException
           Class c = Outer.class;
           Field f = c.getDeclaredField("outerInt");
           f.set(Outer.this23);
      }
  }

   public static void main(String[] args) throws Exception {
       new Outer().new Inner().test();
  }
}

JDK 11开始,嵌套是一种访问控制上下文,它允许多个class同属一个逻辑代码块,但是被编译成多个分散的class文件,它们访问彼此的私有成员无需通过编译器添加访问扩展方法,而是可以直接进行访问,如果上述代码可以直接通过反射访问外部类的私有成员,而不会出现权限问题,请看如下代码:

class Outer {
   private int outerInt;

   class Inner {
       public void test() throws Exception {
           System.out.println("Outer int = " + outerInt);
           // JDK 11之后,如下代码不会出现异常
           Class c = Outer.class;
           Field f = c.getDeclaredField("outerInt");
           f.set(Outer.this23);
      }
  }

   public static void main(String[] args) throws Exception {
       new Outer().new Inner().test();
  }
}

String新增处理方法

String新增处理方法

如以下所示,JDK11新增了一些使用的String处理方法。

// 判断字符串是否为空白
System.out.println(" ".isBlank()); // true
// 去除首尾空白
System.out.println(" baidu ".strip()); // 可以去除全角的空白字符
System.out.println(" baidu".trim()); // 不能去除全角的空白字符
// 去除尾部空格
System.out.println(" baidu ".stripTrailing());
// 去除首部空格
System.out.println("baidu ".stripLeading());
// 复制字符串
System.out.println"baidu".repeat(3)); // baidubaidubaidu
// 行数统计
System.out.println("A
B
C".lines().count()); // 3;

集合新增的API

集合新增的toArray方法

// 1.创建一个不可变集合对象
List<String> names = List.of("猪八戒","猪刚鬣","唐僧");
System.out.println(names);
// names.add("孙悟空");

// 2.可以把List集合转换成对应元素的类型
// Object[] arrs1 = names.toArray();
// JDK 11之前的方案
//String[] arrs1 = names.toArray(new String[0]);
// JDK 11之后的方法
String[] arrs1 = names.toArray(String[]::new);

更方便的IO

IO中新增的方法

Path

of(String, String...)

此前我们需要使用 Paths.get()。目前,我们像其他类一样使用 of()。

Files

writeString(Path, CharSequence) 我们可以使用该方法来保存一个 String 字符串。

Files.writeString(Path.of("test.txt"), "Hello!!!")

readString(Path) 我们可以使用该方法来读取一个 String 字符串。

Files.readString(Path.of("test.txt"), StandardCharsets.UTF_8);

Reader

nullReader() 使用该方法,我们可以得到一个不执行任何操作的 Reader。

WriternullWriter() 使用该方法,我们可以得到一个不执行任何操作的 Writer。


InputStreamnullInputStream() 使用该方法,我们可以得到一个不执行任何操作的 InputStream。InputStream 还终于有了一个超级有用的方法:transferTo,可以用来将数据直接传输到 OutputStream,这是在处理原始数据流时超级常见的一种用法,如下示例。

// InputStream 还终于有了一个超级有用的方法:transferTo
try (
   InputStream is = new FileInputStream("demo.txt");
   OutputStream os = new FileOutputStream("test.txt");
) {
   // 开始把字节输入流中的全部字节数据转移到字节输出流中去。
   is.transferTo(os);
   System.out.println("复制完成!");

} catch (Exception e) {
   e.printStackTrace();
}


OutputStreamnullOutputStream() 使用该方法,我们可以得到一个不执行任何操作的 OutputStream。

标准Java HTTP客户端

取代繁琐的HttpURLConnection的请求。

从 Java 9 开始引入了一个处理 HTTP 请求的的 HTTP Client API,不过当时一直处于孵化阶段,而在 Java 11 中已经为正式可用状态,作为一个标准API提供在java.net.http供大家使用,该 API 支持同步和异步请求。

动机

JDK8中的HttpURLConnectionAPI及实则现存在许多问题:

  1. URLConnection API是设计时思考了多种协议,而这些都协议是目前已经不存在(ftp,gopher,等)。
  2. API早于HTTP / 1.1并且过于抽象。
  3. 难于使用
  4. 仅能在阻塞模式下工作
  5. 难于维护

优势

HTTP Client的优势

  1. API必须是易于使用的,包括简单的阻塞模式
  2. 必须支持通知机制如HTTP消息头收到、错误码、HTTP消息体收到
  3. 简洁的API能够支持80-90%的需求
  4. 必须支持标准和通用身份验证机制
  5. 必须能够轻松使用WebSocket
  6. 必须支持HTTP 2
  7. 必须执行与现有网络API一致的安全检查
  8. 必须对lambda表达式等新语言功能很友善
  9. 应该对嵌入式系统友善,避免永久运行的后台线程
  10. 必须支持HTTPS / TLS
  11. 满足HTTP 1.1和HTTP 2的性能要求

使用需求:使用Http Client请求如下网址内容:

http://api.k780.com:88/?
app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json

来看一下 HTTP Client 的用法:

/**
* JDK 11之后:Http Client
*/
public class Demo07 {
   /**
    * 发起同步请求
    */
   @Test
   public void send01() throws Exception {
       // 地址
       String url = "http://api.k780.com:88/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
       // 1.创建一个HttpClient客户都对象
       HttpClient client = HttpClient.newHttpClient();

       // 2.创建一个请求对象:request ,封装地址,参数,请求方式(Get Post)
       // 注意:GET()方法的调用可以省略,默认就是get请求。
       HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(url)).GET().build();

       // 3.通过HttpClient对象发起request请求得到一个响应结果对象
       /**
        * 参数一:请求对象
        * 参数二:响应的结果处理成字符串结果。
        * 返回的是一个响应对象。
        */
       HttpResponse<String> response = client.send(request
              ,HttpResponse.BodyHandlers.ofString());

       // 4.得到响应码
       int code = response.statusCode();
       // 5.得到响应的结果数据:字符串
       String rs = response.body();
       // 6.输出结果
       System.out.println(code+" : " + rs);
  }

   /**
    * 发起异步请求
    */
   @Test
   public void send02(){
       // 地址
       String url = "http://api.k780.com:88/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
       // 1.创建一个HttpClient客户都对象
       HttpClient client = HttpClient.newHttpClient();

       // 2.创建一个请求对象:request ,封装地址,参数,请求方式(Get Post)
       // 注意:GET()方法的调用可以省略,默认就是get请求。
       HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(url)).GET().build();

       // 3.通过客户端对象发起request的异步请求
       CompletableFuture<HttpResponse<String>> future =
               client.sendAsync(request,HttpResponse.BodyHandlers.ofString());

       // 4.异步:监听结果数据
       future.whenComplete((stringHttpResponse,throwable) -> {
               // 5.结果数据的处理
               if(throwable != null){
                   // 请求出错了
                   throwable.printStackTrace();
              }else{
                   int code = stringHttpResponse.statusCode();
                   String rs = stringHttpResponse.body();
                   System.out.println(code+"->"+rs);
              }
      }).join(); // 阻塞等待异步结果。

       System.out.println("结束程序!");
  }
}

Unicode 10

JDK11中升级现有平台的API,支持Unicode 10,Unicode10的标准请参考网站(
http://unicode.org/versions/Unicode10.0.0)

Unicode 10 增加了8518个字符,,总计达到了136690个字符。并且增加了4个脚本.同时还有56个新的emoji表情符号。目前支持最新的Unicode的类主要有java.lang包下的Character, Stringjava.awt.font下的相关类java.text包下的Bidi,Normalizer等

String对Unicode的示例:

System.out.println("uD83EuDD93"); // 
System.out.println("uD83EuDD92"); // 
System.out.println("uD83EuDDDA"); // 
System.out.println("uD83EuDDD9"); // 
System.out.println("uD83EuDDD1"); // 
System.out.println("uD83EuDDD8"); // 
System.out.println("uD83EuDD95"); // 
System.out.println("uD83EuDD2e"); // 

改善Aarch64函数

改善现有的字符串和数组函数,并在Aarch64处理器上为java.lang.Math 下的sin , cos 和log函数实现新的内联函数。从而实现专用的CPU架构下提高应用程序的性能。

代码示例:

public static void main(String[] args) {
   long startTime = System.nanoTime();
   for(int i = 0 ; i < 10000000 ; i++ ){
       Math.sin(i);
       Math.cos(i);
       Math.log(i);
  }
   long endTime = System.nanoTime();
   // JDK 11下耗时:1564ms
   // JDK 8前耗时:10523ms
   System.out.println(TimeUnit.NANOSECONDS.toMillis(endTime-startTime)+"ms");
}

更简化的编译运行程序

在JDK11以前要运行一个Java程序,需要先使用javac 对xxx.java进行编译,形成xxx.class,再通过java xxx运行class文件。在JDK11中可以直接使用java命令运行一个xxx.java文件

<img src="https://img.niaorui.com/blogimg/20251031/053f5a61bbb54e289f3b21db7dcb55a8.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

Epsilon垃圾收集器

无操作垃圾收集器(Epsilon: A No-Op Garbage Collector)

JDK上对这个特性的描述是: 开发一个处理内存分配但不实现任何实际内存回收机制的GC, 一旦java的堆被耗尽,JVM就直接关闭。如果有System.gc()调用, 实际上什么也不会发生垃圾对象的回收操作(这种场景下和-XX:+DisableExplicitGC效果一样),由于没有内存回收, 这个实现可能会警告用户尝试强制GC是徒劳。

先使用G1垃圾收集器

public class Demo08 {
   public static void main(String[] args) {
       System.out.println("程序开始。。。");
       List<Garbage> lists = new ArrayList<>();
       int count = 0 ;
       while(true){
           lists.add(new Garbage(lists.size()));
           if(lists.size() == 1000000 && count == 0){
               lists.clear();
               count++;
          }
      }
       // System.out.println("程序结束。。。");
  }
}


class Garbage{
   private int num ;
   public Garbage(int num){
       this.num = num;
  }

   @Override
   protected void finalize() throws Throwable {
       System.out.println(this+" is dying!");
  }
}

启动参数(设置堆内存空间大小):

-Xms100m -Xmx100m

运行程序后,结果如下:

<img src="https://img.niaorui.com/blogimg/20251031/372e7dd0a09342f18f3c971c70a70cd0.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

会发现G1一直回收对象,直到内存不够用。

使用Epsilon垃圾收集器


UnlockExperimentalVMOptions:解锁隐藏的虚拟机参数

-XX:+UnlockExperimentalVMOptions
-XX:+UseEpsilonGC
-Xms100m
-Xmx100m

如果使用选项-XX:+UseEpsilonGC, 程序很快就由于堆空间不足而退出。运行程序后,结果如下:

<img src="https://img.niaorui.com/blogimg/20251031/764157eb0c1d46f5affe49ca6900f361.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

会发现很快就内存溢出了,由于Epsilon不会去回收对象。

Epsilon垃圾收集器主要用途

  1. Performance testing: 性能测试(它可以协助过滤掉GC引起的性能假象)
  2. Memory pressure testing, 在测试java代码时,确定分配内存的阈值有助于设置内存压力常量值。这时no-op就很有用,它可以简单地接受一个分配的内存分配上限,当内存超限时就失败。例如:测试需要分配小于1G的内存,就使用-Xmx1g参数来配置no-op GC,然后当内存耗尽的时候就直接crash
  3. 超级短的JOB任务(对象这种任务, 接受GC清理堆那都是浪费空间)
  4. VM interface testing, 以VM开发视角,有一个简单的GC实现,有助于理解VM-GC的最小接口实现。它也用于证明VM-GC接口的健全性。
  5. Last-drop latency improvements, 对那些极端延迟敏感的应用,开发者十分清楚内存占用,或者是几乎没有垃圾回收的应用,此时耗时较长的GC周期将会是一件坏事。
  6. Last-drop throughput improvements, 即便对那些无需内存分配的工作,选择一个GC意味着选择了一系列的GC屏障,所有的OpenJDK GC都是分代的,所以他们至少会有一个写屏障。避免这些屏障可以带来一点点的吞吐量提升。

ZGC 可伸缩低延迟垃圾收集器

目标

GC是Java主要优势之一。不过,当GC停顿太长,就会开始影响应用的响应时间。消除或者减少GC停顿时长,Java将对更广泛的应用场景是一个更有吸引力的平台。此外,现代系统中可用内存不断增长, 用户和程序员希望JVM能够以高效的方式充分利用这些内存,并且无需长时间的GC暂停时间。

今天,应用程序同时为数千甚至数百万用户提供服务的情况并不少见。这些应用程序需要大量内存。但是,管理所有内存可能会轻易影响应用程序性能。为了解决这个问题,Java 11引入了Z垃圾收集器(ZGC)作为实验性垃圾收集器(GC)实现。

ZGC全称是Z Garbage Collector,是一款可伸缩(scalable)的低延迟(low latency garbage)、并发(concurrent)垃圾回收器,旨在实现以下几个目标:

  1. 停顿时间不超过10ms
  2. 停顿时间不随heap大小或存活对象大小增大而增大
  3. 可以处理从几百G到几TB的内存大小,远剩余前一代的G1。
  4. 初始只支持64位系统;

用法

运行我们的应用程序时,我们可以使用以下命令行选项启用ZGC:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
请注意:由于ZGC还处于实验阶段,所以需要通过JVM参数UnlockExperimentalVMOptions 来解锁这个特性。

平台支持

ZGC目前只在Linux/x64上可用,如果有足够的需求,将来可能会增加对其他平台的支持。目前只支持64位的linux系统。

ZGC和G1停顿时间比较:

ZGC
avg: 1.021ms (+/-0.215ms)
95th percentile: 1.392ms
99th percentile: 1.512ms
99.9th percentile: 1.663ms
99.99th percentile: 1.681ms
max: 1.681ms

G1
avg: 157.202ms (+/-71.126ms)
95th percentile: 316.672ms
99th percentile: 428.095ms
99.9th percentile: 543.846ms
99.99th percentile: 543.846ms
max: 543.846ms

其他新特性

移除项

1、移除了com.sun.awt.AWTUtilities
2、移除了sun.misc.Unsafe.defineClass,使用java.lang.invoke.MethodHandles.Lookup.defineClass来替
代
3、移除了Thread.destroy()以及 Thread.stop(Throwable)方法
4、移除了sun.nio.ch.disableSystemWideOverlappingFileLockChecksun.locale.formatasdefault属性
5、移除了jdk.snmp模块
6、移除了javafxopenjdk估计是从java10版本就移除了,oracle jdk10还尚未移除javafx,而java11版本则
oraclejdk版本也移除了javafx
7、移除了Java Mission Control,从JDK中移除之后,需要自己单独下载
8、移除了这些Root CertificatesBaltimore Cybertrust Code Signing CASECOMAOL and Swisscom

废弃项

1、-XX+AggressiveOpts选项
2、-XX:+UnlockCommercialFeatures
3、-XX:+LogCommercialFeatures选项也不再需要

移除Java EE和CORBA模块(Remove the Java EE and CORBA Modules)

1、java.xml.ws,
2、java.xml.bind,
3、java.xml.ws,
4、java.xml.ws.annotation,
5、jdk.xml.bind,
6、jdk.xml.ws被移除,
只剩下java.xmljava.xml.crypto,jdk.xml.dom这几个模块
7、java.corba,
8、java.se.ee,
9、java.activation,
10、java.transaction被移除,但是java11新增一个java.transaction.xa模块

JEP : 335 : Deprecate the Nashorn JavaScript Engine

废除Nashorn javascript引擎,在后续版本准备移除掉。

JEP : 336 : Deprecate the Pack200 Tools and API

Java5中带了一个压缩工具:Pack200,这个工具能对普通的jar文件进行高效压缩。实则现原理是根据Java类特有的结构,合并常数池,去掉无用信息等来实现对java类的高效压缩。由于是专门对Java类进行压缩的,所以对普通文件的压缩和普通压缩软件没有什么两样,但是对于Jar 文件却能轻易达到10-40%的压缩率。这在Java应用部署中很有用,尤其对于移动Java计算,能够大大减小代码下载量。

Java5中还提供了这一技术的API接口,你可以将其嵌入到你的程序中使用。使用的方法很简单,下面的短短几行代码即可以实现jar的压缩和解压:

压缩
Packer packer=Pack200.newPacker();
OutputStream output=new BufferedOutputStream(new FileOutputStream(outfile));
packer.pack(new JarFile(jarFile), output);
output.close();
解压
Unpacker unpacker=Pack200.newUnpacker();
output=new JarOutputStream(new FileOutputStream(jarFile));
unpacker.unpack(pack200File, output);
output.close();

Pack200的压缩和解压缩速度是比较快的,而且压缩率也是很惊人的,在我是使用的包4.46MB压缩后成了1.44MB(0.322%),而且随着包的越大压缩率会根据明显,据说如果jar包都是class类可以压缩到1/9的大 小。实则JavaWebStart还有许多功能,例如可以按不同的jar包进行lazy下载和单独更新,设置可以根据jar中的类变动进行class粒度的下载。但是在java11中废除了pack200以及unpack200工具以及java.util.jar中的Pack200 API。由于Pack200主要是用来压缩jar包的工具,由于网络下载速度的提升以及java9引入模块化系统之后不再依赖Pack200,因此这个版本将其移除掉。

JEP 332: Transport Layer Security (TLS) 1.3

实现TLS协议1.3版本。(TLS允许客户端和服务端通过互联网以一种防止窃听,篡改以及消息伪造的方式进行通信)。

TLS 1.3是TLS协议的重大改善,与以前的版本相比,它提供了显着的安全性和性能改善。其他供应商的几个早期实现已经可用。我们需要支持TLS 1.3以保持竞争力并与最新标准保持同步。这个特性的实现动机和Unicode 10一样,也是紧跟历史潮流。

JEP 328: Flight Recorder

提供一个低开销的,为了排错Java应用问题,以及JVM问题的数据收集框架,希望达到的目标如下:

  1. 提供用于生产和消费数据作为事件的API;
  2. 提供缓存机制和二进制数据格式;
  3. 允许事件配置和事件过滤;
  4. 提供OS,JVM和JDK库的事件;

动机排错,监控,性能分析是整个开发生命周期必不可少的一部分,但是某些问题只会在大量真实数据压力下才会发生在生产环境。

Flight Recorder记录源自应用程序,JVM和OS的事件。事件存储在一个文件中,该文件可以附加到错误报告中并由支持工程师进行检查,允许事后分析导致问题的时期内的问题。工具可以使用API从记录文件中提取信息。

Flight Recorder的名字来源有点像来自于飞机的黑盒子,一种用来记录飞机飞行情况的的仪器。而FlightRecorder就是记录Java程序运行情况的工具。

JDK12新特性

switch表达式

JDK12中的switch表达式

Java的switch语句是一个变化较大的语法(可能是由于Java的switch语句一直不够强劲、熟悉swift或者js语言的同学可与swift的switch语句对比一下,就会发现Java的switch相对较弱),由于Java的许多版本都在不断地改善switch语句。

JDK12扩展了switch语句,使其可以用作语句或者表达式,并且传统的和扩展的简化版switch都可以使用。JDK 12对于switch的增强主要在于简化书写形式,提升功能点。

下面简单回顾一下switch的进化阶段:

  1. 从Java 5+开始,Java的switch语句可使用枚举了。
  2. 从Java 7+开始,Java的switch语句支持使用String类型的变量和表达式了。
  3. 从Java 11+开始,Java的switch语句会自动对省略break导致的贯穿提示警告(以前需要使用-X:fallthrough选项才能显示出来) 。
  4. 从JDK12开始,Java的switch语句有了很大程度的增强。

JDK12以前的switch程序

代码如下:

/**
   JDK 12之前的switch语句写法。
*/
public class Demo01 {
   public static void main(String[] args) {
       // 声明变量score,并为其赋值为'C'
       var score = 'C';
       // 执行switch分支语句
       switch (score) {
           case 'A':
               System.out.println("优秀");
           case 'B':
               System.out.println("良好");
           case 'C':
               System.out.println("中");
           case 'D':
               System.out.println("及格");
           case 'E':
               System.out.println("不及格");
               break;
           default:
               System.out.println("数据非法!");
      }
  }
}

这是经典的Java 11以前的switch写法 ,这里不能忘记写break,否则switch就会贯穿、导致程序出现错误(JDK 11会提示警告)。

JDK12不需要break了

在JDK 12之前如果switch忘记写break将导致贯穿,在JDK 12中对switch的这一贯穿性做了改善。你只要将case后面的冒号(:)改成箭头,那么你即使不写break也不会贯穿了,因此上面程序可改写如下形式:

/**
* JDK 12开始switch的新写法:不需要写break了。
*/
public class Demo02 {
   public static void main(String[] args) {
       var score = 'B';
       switch (score){
           case 'A' -> System.out.println("优秀");
           case 'B' -> System.out.println("良好");
           case 'C' -> System.out.println("中");
           case 'D' -> System.out.println("及格");
           case 'E' -> System.out.println("较差");
           default -> System.out.println("成绩数据非法!");
      }
  }
}

上面代码简洁许多了。

JDK12的switch表达式

Java 12的switch甚至可作为表达式了——不再是单独的语句。例如如下程序。

/**
* JDK 12开始switch可以作为一个表达式返回执行的结果了。
*/
public class Demo03 {
   public static void main(String[] args) {
       var score = 'B';
       String rs = switch (score) {
           case 'A' -> "优秀";
           case 'B' -> "良好";
           case 'C' -> "中";
           case 'D' -> "及格";
           case 'E' -> "较差";
           default -> "成绩数据非法!";
      };
       System.out.println("执行的结果:" + rs);
  }
}

上面程序直接将switch表达式的值赋值给s变量,这样switch不再是一个语句,而是一个表达式.

JDK12中switch的多值匹配

当你把switch中的case后的冒号改为箭头之后,此时switch就不会贯穿了,但在某些情况下,程序本来就希望贯穿列如我就希望两个case共用一个执行体!JDK 12的switch中的case也支持多值匹配,这样程序就变得更加简洁了。例如如下程序。

/**
* JDK 12开始switch可以支持多值匹配
*/
public class Demo04 {
   public static void main(String[] args) {
       var score = 'D';
       String rs = switch (score){
           case 'A','B' -> "优秀";
           case 'C','D' -> "中等";
           case 'E' -> "一般";
           default -> "成绩数据非法!";
      };
       System.out.println("执行的结果:"+rs);
  }
}

小结

从以上案例可以看出JDK 12对switch的功能做了很大的改善,代码也十分的简化,目前来看switch依然是不支持区间匹配的,未来是否可以支持,我们拭目以待。

微基准测试套件

JEP 230: Microbenchmark Suite 微基准测试套件

JMH是什么

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

JMH比较典型的应用场景

  1. 想定量地知道某个方法需要执行多长时间,以及执行时间和输入 n 的相关性
  2. 一个接口有两种不同实现(例如实现 A 使用了 FixedThreadPool,实现 B 使用了 ForkJoinPool),不知道哪种实现性能更好。

JMH的使用案例

如果你使用 maven 来管理你的 Java 项目的话,引入 JMH 是一件很简单的事情——只需要在pom.xml 里增加 JMH的依赖即可

<properties>
   <jmh.version>1.14.1</jmh.version>
</properties>

<dependencies>
   <dependency>
       <groupId>org.openjdk.jmh</groupId>
       <artifactId>jmh-core</artifactId>
       <version>${jmh.version}</version>
   </dependency>
   <dependency>
       <groupId>org.openjdk.jmh</groupId>
       <artifactId>jmh-generator-annprocess</artifactId>
       <version>${jmh.version}</version>
       <scope>provided</scope>
   </dependency>
</dependencies>

<build>
   <plugins>
       <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.8.0</version>
           <configuration>
               <release>12</release>
               <compilerArgs>--enable-preview</compilerArgs>
           </configuration>
       </plugin>
   </plugins>
</build>

接下来再创建我们的第一个 Benchmark

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class HelloWorld {

   @Benchmark
   public int testSleep01() {
       try {
           Thread.sleep(300);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       return 0;
  }

   public static void main(String[] args) throws RunnerException {
       Options opt = new OptionsBuilder()
              .include(HelloWorld.class.getSimpleName())
              .forks(1)
              .warmupIterations(5)
              .measurementIterations(5)
              .build();

       new Runner(opt).run();
  }
}

注解@BenchmarkMode对应Mode选项,可用于类或者方法上, 需要注意的是,这个注解的value是一个数组,可以把几种Mode集合在一起执行,还可以设置为Mode.All,即全部执行一遍。

@State类注解,JMH测试类必须使用@State注解,State定义了一个类实例的生命周期,可以类比Spring Bean的Scope。由于JMH允许多线程同时执行测试,不同的选项含义如下:

Scope.Thread:默认的State,每个测试线程分配一个实例;

Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;

Scope.Group:每个线程组共享一个实例;

@OutputTimeUnitbenchmark 结果所使用的时间单位,可用于类或者方法注解,使用
java.util.concurrent.TimeUnit中的标准时间单位。
@Benchmark方法注解,表明该方法是需要进行 benchmark 的对象。

启动项

public static void main(String[] args) throws RunnerException {
   Options opt = new OptionsBuilder()
          .include(HelloWorld.class.getSimpleName())
          .forks(1)
          .warmupIterations(5)
          .measurementIterations(5)
          .build();

   new Runner(opt).run();
}

include(
SimpleBenchmark.class.getSimpleName())代表我要测试的是哪个类的方法

exclude(“xxx”)代表测试的时候需要排除xxxx方法

forks(2)指的是做2轮测试,在一轮测试无法得出最满意的结果时,可以多测几轮以便得出更全面的测试结果,而每一轮都是先预热,再正式计量。

warmupIterations(5) 代表先预热5次

measurementIterations(5) 正式运行测试5次

参数详解

Mode

Mode 表明 JMH 进行 Benchmark 时所使用的模式。一般是测量的维度不同,或是测量的方式不同。目前 JMH 共有四种模式:

Throughput: 吞吐量,一段时间内可执行的次数,每秒可执行次数
AverageTime: 每次调用的平均耗时时间。
SampleTime: 随机进行采样执行的时间,最后输出取样结果的分布
SingleShotTime: 在每次执行中计算耗时,以上模式都是默认一次 iteration 是 1s,只有 SingleShotTime 是只运
行一次。

IterationIteration 是 JMH 进行测试的最小单位。在大部分模式下,一次 iteration 代表的是一秒,JMH 会在这一秒内不断调用需要 benchmark 的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。

WarmupWarmup 是指在实际进行 benchmark 前先进行预热的行为。为什么需要预热?由于 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。为了让 benchmark 的结果更加接近真实情况就需要进行预热

测试结果如下

JDK12中JMH说明

Java 12 中添加一套新的基本的微基准测试套件(microbenchmarks suite):

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

默认生成类数据共享

JEP 341: Default CDS Archives 默认生成类数据共享

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

好处是:JVM启动时间减少了,另外核心类是共享的,所以JVM的内存占用也减少了。

Java12新特性

  1. JDK 12之前,想要利用CDS的用户,即使仅使用JDK中提供的默认类列表,也必须 java -Xshare:dump 作为额外的步骤来运行。
  2. Java 12 针对 64 位平台下的 JDK 构建过程进行了增强改善,使其默认生成类数据共享(CDS)归档,以进一步达到改善应用程序的启动时间的目的。
  3. 同时也撤销了用户必须手动运行:java -Xshare:dump 才能使用CDS的功能。

Shenandoah GC低停顿时间的GC(预览)

JEP 189:Shenandoah:A Low-Pause-Time Garbage Collector(Experimental) 低暂停时间的GC

添加一个名为Shenandoah的新垃圾收集(GC)算法,通过与正在运行的Java线程同时进行疏散工作来减少GC暂停时间,最终目标旨在针对 JVM 上的内存收回实现低停顿的需求。

使用Shenandoah的暂停时间与堆大小无关,这意味着无论堆是200MB还是200GB,您都将具有一样的一致暂停时间。与 ZGC 类似,Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等

使用

-XX:+UnlockExperimentalVMOptions
在命令行中要求。作为实验性功能,Shenandoah构建系统会自动禁用不受支持的配置。
要启用/使用Shenandoah GC,需要以下JVM选项:
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

G1垃圾收集器功能增强

344:Abortable Mixed Collections for G1 可中止的G1 Mixed GC

如果G1垃圾收集器有可能超过预期的暂停时间,则可以使用终止选项。

该垃圾收集器设计的主要目标之一是满足用户设置的预期的 JVM 停顿时间,可以终止可选部分的回收已到达停顿时间的目标。

346:Promptly Return Unused Committed Memory from G1 G1及时返回未使用的已分配内存如果应用程序活动超级低,G1应该在合理的时间段内释放未使用的Java堆内存。

G1可以使其能够在空闲时自动将 Java 堆内存返还给操作系统

JDK12其他新特性

增加项:String新增方法

  1. transform(Function):对字符串进行处理后返回。
  2. 2indent :该方法允许我们调整String实例的缩进
/**
* JDK 12 String新增方法。
*/
public class Demo01 {
   public static void main(String[] args) {
       var rs = "baidu".transform(s -> s +"学习api!").transform(s -> s.toUpperCase());
       System.out.println(rs);
       System.out.println("=======================");
       var rs1 = "A
BC
".indent(3);
       System.out.println(rs1);
  }
}

新增项:Files新增mismatch方法

返回内容第一次不匹配的字符位置索引

/**
* JDK 12 : Files新增mismatch方法
*/
public class Demo01 {
   public static void main(String[] args) throws Exception {
       Writer fw = new FileWriter("a.txt");
       fw.write("add");
       fw.write("bbb");
       fw.write("ccc");
       fw.close();

       Writer fw1 = new FileWriter("b.txt");
       fw1.write("add");
       fw1.write("bbb");
       fw1.write("ccxc");
       fw1.close();
       // 两个文件的匹配:mismatch方法
       // 如果两个文件没有不匹配的,完全匹配返回-1.
       // 如果不匹配会返回第一个不匹配的字符索引值
       System.out.println(Files.mismatch(Path.of("a.txt") ,Path.of("b.txt") ));
  }
}

核心库java.text支持压缩数字格式

NumberFormat添加了对以紧凑形式格式化数字的支持。紧凑数字格式是指以简短或人类可读形式表明的数字。例如,在en_US语言环境中,1000可以格式化为“1K”,1000000可以格式化为“1M”,具体取决于指定的样式NumberFormat.Style。紧凑数字格式由LDML的Compact Number格式规范定义。要获取实例,请使用NumberFormat紧凑数字格式所给出的工厂方法之一。

public class Demo01 {
   public static void main(String[] args) {
       //例如:
       NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
       String result = fmt.format(1000);
       System.out.println(result);
       // 上面的例子导致“1K”。

       var cnf = NumberFormat.getCompactNumberInstance(Locale.CHINA,
               NumberFormat.Style.SHORT);
       System.out.println(cnf.format(3_0000));
       System.out.println(cnf.format(3_4200)); // 四舍五入
       System.out.println(cnf.format(3_000_000));
       System.out.println(cnf.format(3L << 30));
       System.out.println(cnf.format(3L << 50));
       System.out.println(cnf.format(3L << 60));
  }
}

输出结果:

<img src="https://img.niaorui.com/blogimg/20251031/86bb0051a5764cd3859af4baafe4708e.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

核心库java.lang中支持Unicode 11JDK 12版本包括对Unicode 11.0.0的支持。在发布支持Unicode 10.0.0的JDK 11之后,Unicode 11.0.0引入了以下JDK 12中包含的新功能:

1、684个新角色
1.1、66个表情符号字符
1.2、Copyleft符号
1.3、评级系统的半星
1.4、额外的占星符号
1.5、象棋中国象棋符号
2、11个新区块
2.1、格鲁吉亚扩展
2.2、玛雅数字
2.3、印度Siyaq数字
2.4国际象棋符号
3、7个新脚本
3.1、Hanifi Rohingya
3.2、Old Sogdian
3.3、Sogdian
3.4、Dogra
3.5、Gunjala Gondi
3.6、Makasar
3.7、Medefaidrin

移除项

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

废弃项

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

JDK13新特性

JDK 13于9月17日发布,目前Java的更新速度超级快,每6个月发布一个新的Java版本。JDK13下载地址:
https://www.oracle.com/technetwork/java/javase/downloads/jdk13-downloads-5672538.html

<img src="https://img.niaorui.com/blogimg/20251031/a4c9e5b7dd2b44e5b457eef50efece9b.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

Java 13 新特性

JDK 13 新特性链接地址:
http://openjdk.java.net/projects/jdk/13/

<img src="https://img.niaorui.com/blogimg/20251031/8f1876adf95840a2b4608ecb459797fc.jpg" alt="JDK8后来新特性”>” title=”JDK8后来新特性”>

(1)350:Dynamic CDS Archives 动态CDS存档。扩展应用程序类 – 数据共享( application class-data sharing),以允许在Java应用程序执行结束时动态归档类。归档类将包括默认的基础层CDS存档中不存在的所有已加载的应用程序类和库类。

(2)351:ZGC: 增强ZGC以将未使用的堆内存返回给操作系统。

(3)353:重新实现旧版套接字API:维护和调试的更简单,新代码替换java.net.Socket和java.net.ServerSocket API的底层实现。新的实现很容易适应用户模式线程threads。

(4)354:Switch 表达式(预览):JDK12继续迭代。

(5)355:Text Blocks文本块 (预览):将文本块添加到Java语言。文本块是一个多行字符串文字,它避免了对大多数转义序列的需要,以可预测的方式自动格式化字符串,并在需要时让开发人员控制格式。

JEP 355:Text Blocks文本块

JDK13之前:使用“一维”字符串文字

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

JDK13优化的:使用“二维”文本块

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

SQL示例使用“一维”字符串文字

String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
" +
              "WHERE `CITY` = 'INDIANAPOLIS'
" +
              "ORDER BY `EMP_ID`, `LAST_NAME`;
";

使用“二维”文本块

String query = """
              SELECT `EMP_ID``LAST_NAME` FROM `EMPLOYEE_TB`
              WHERE `CITY` = 'INDIANAPOLIS'
              ORDER BY `EMP_ID``LAST_NAME`;
              """;

多种语言示例使用“一维”字符串文字

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {
" +
                        "   print('"Hello, world"');
" +
                        "}
" +
                        "
" +
                        "hello();
");

使用“二维”文本块

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("""
                        function hello() {
                            print('"Hello, world"');
                        }
                        
                        hello();
                        """);

解释文本块:

"""
line 1
line 2
line 3
"""

等效于字符串文字:

"line 1
line 2
line 3
"

或字符串文字的串联:

"line 1
" +
"line 2
" +
"line 3
"

如果在字符串的末尾不需要行终止符,则可以将结束定界符放在内容的最后一行。例如,文本块:

"""
line 1
line 2
line 3"""

等效于字符串文字:

"line 1
line 2
line 3"

JEP 354:Switch 表达式

JDK11以及之前的版本:

switch (day) {
   case MONDAY:
   case FRIDAY:
   case SUNDAY:
        System.out.println(6);
        break;
   case TUESDAY:
       System.out.println(7);
       breakcase THURSDAY:
   case SATURDAY:
       System.out.println(8);
        break;
   case WEDNESDAY:
        System.out.println(9);
        break;
}

JDK12版本

switch (day) {
   case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
   case TUESDAY -> System.out.println(7);
   case THURSDAY, SATURDAY -> System.out.println(8);
   case WEDNESDAY -> System.out.println(9);
}

JDK13版本

int numLetters = switch (day) {
   case MONDAY, FRIDAY, SUNDAY -> 6;
   case TUESDAY                -> 7;
   case THURSDAY, SATURDAY     -> 8;
   case WEDNESDAY              -> 9;
};

JEP 350:动态CDS存档

提高应用程序类 – 数据共享(AppCDS)的可用性。消除了用户进行试运行以创建每个应用程序的类列表的需要。-Xshare:dump 使用类列表由该选项启用的静态归档应继续工作。这包括内置类加载器和用户定义的类加载器的类。

JEP 351:ZGC:撤销提交未使用的内存

ZGC当前不会撤销提交并将内存返回给操作系统,即使该内存已经使用了很长时间也是如此。对于所有类型的应用程序和环境,尤其是那些关注内存占用的应用程序和环境,此行为都不是最佳的。增强ZGC以将未使用的堆内存返回给操作系统。

JEP 353:重新实现旧版套接字API

摘要:

用更易于维护和调试的更简单,更现代的实现来替换java.net.Socketjava.net.ServerSocketAPI 使用的基础实现。新的实现将很容易适应与用户模式线程(也就是光纤)一起使用,当前正在Project Loom中进行探索。

动机:

在java.net.Socket和java.net.ServerSocketAPI,以及它们的底层实现,可以追溯到JDK 1.0。实现是遗留Java和C代 码的混合,维护和调试很痛苦。该实现使用线程堆栈作为I/O缓冲区,这种方法需要多次增加默认线程堆栈大小。该实现使用本机数据 结构来支持异步关闭,这是多年来微妙可靠性和移植问题的根源。该实现还有几个并发问题,需要进行大修才能正确解决。在未来的光 纤世界环境中,而不是在本机方法中阻塞线程,当前的实现不适用于目的。

FileSystems.newFileSystem新方法

核心库/ java.nio中添加了FileSystems.newFileSystem(Path,Map <String,?>)方法

添加了三种新方法java.nio.file.FileSystems,以便更轻松地使用将文件内容视为文件系统的文件系统提供程序。1、newFileSystem(Path)2、newFileSystem(Path, Map<String, ?>)3、newFileSystem(Path, Map<String, ?>, ClassLoader)

添加为newFileSystem(Path, Map<String, ?>) 已使用现有2-arg newFileSystem(Path, ClassLoader)并指定类加载器 的代码创建源(但不是二进制)兼容性问题。null.例如,由于引用newFileSystem不明确,因此无法编译以下内容: FileSystem fs = FileSystems.newFileSystem(path, null); 为了避免模糊引用,需要修改此代码以将第二个参数强制转换为java.lang.ClassLoader。

NIO新方法

核心库/ java.nio中新的java.nio.ByteBuffer批量获取/放置方法转移字节而不思考缓冲区位置。


java.nio.ByteBufferjava.nio目前,其他缓冲区类型定义绝对批量get和put传输连续字节序列的方法,而不思考或影响缓冲区位置。

核心库/java.time

新日本时代名称Reiwa,此更新中添加了代表新Reiwa时代的实例。与其他时代不同,这个时代没有公共领域。它可以通过调用 JapaneseEra.of(3)或获得JapaneseEra.valueOf(“Reiwa”)。JDK13及更高版本将有一个新的公共领域来代表这个时代。 NewEra从2019年5月1日开始的日本时代的占位符名称“ ”已被新的官方名称取代。依赖占位符名称(请参阅JDK-8202088)获取新时代单例(JapaneseEra.valueOf(“NewEra”))的应用程序将不再起作用。请参阅JDK-8205432

核心库/ java.util中:I18N

支持Unicode 12.1,此版本将Unicode支持升级到12.1,其中包括以下内容:

java.lang.Character支持12.1级的Unicode字符数据库,其中12.0从11.0开始增加554个字符,总共137,928个 字符。这些新增内容包括4个新脚本,总共150个脚本,以及61个新的表情符号字符。U+32FF SQUARE ERA NAME REIWA从 12.0开始,12.1只添加一个字符。java.text.Bidi和java.text.Normalizer类分别支持12.0级的Unicode标准附件, #9和#15。java.util.regexpackage支持基于12.0级Unicode标准附件#29的扩展字形集群。

热点/GC

10.1 JEP 351 ZGC撤销提交未使用的存储器10.2 添加了-XXSoftMaxHeapSize标志10.3 ZGC支持的最大堆大小从4TB增加到16TB

安全库/java.security

1 该
com.sun.security.crl.readtimeout系统属性设置为CRL检索的最大读取超时,单位为秒。如果尚未设置该属性,或者其值为负,则将其设置为默认值15秒。值0表明无限超时。

2 新的keytool -showinfo -tls用于显示TLS配置信息的命令keytool -showinfo -tls添加了一个显示TLS配置信 息的新命令。

11.3 SunMSCAPI提供程序目前支持以下一代加密(CNG)格式读取私钥。这意味着CNG格式的RSA和EC密钥可从Windows密钥 库加载,例如“Windows-MY”。与EC(签名算法SHA1withECDSA,SHA256withECDSA等等)也支持。

删除功能

1 核心库/java.net中,不再支持Pre-JDK 1.4 SocketImpl实现java.net.SocketImpl此版本已删除对为JavaSE1.3及更早版本编译的自定义实现的支持。此更改对SocketImpl为Java SE 1.4(2002年发布)或更新版本编译的实现没有影响。

2 核心库/java.lang中,删除运行时跟踪方法,过时的方法traceInstructions(boolean),并traceMethodCalls(boolean)已经从删除java.lang.Runtime类。这些方法对许多版本都不起作用,它们的预期功能由Java虚拟机工具接口(JVMTI)提供。

END

© 版权声明

相关文章

1 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    中公教育 读者

    收藏了,感谢分享

    无记录