听说JDK14发布了,让我们一起愉快的吐槽他吧~

/ 0评 / 0

听说JDK14发布了,让我们一起愉快的吐槽他吧~

写在最前面

本文娱乐水文,不聊源代码(没有看),不聊实现(不清楚),只谈功能点,所以看看就行别硬杠,本文提到的东西在工作中用处几乎为零(略略略,你不可能用到这些新特性的,虽然现在有,但并不影响你用JDK8),本文的代码均来自官网demo和Kelovp的乱写(我又没安装jdk14,我哪知道行不行),当然你如果不知道9/10/11/12/13改了哪些,英文棒的建议去官网看release note,如果又菜又懒和我一样的话可以去看下别人的fast review版本:https://segmentfault.com/a/1190000022064656
当然你如果就是想看我废话,那也行,往下看。

正文

自从我把Java开发作为我的工作之后(2017-11月值得纪念),虽然已经注意到了oracle宣布一年春秋两更新,但从没想过我还在研究JDK8特性的时候,人家已经更新到了14...

在三月十八号工作空余之时刷了下推特:真就直接更新了
UTOOLS1584699824638.png

既然如此那就让我们愉快的吐槽吧

先声明昂,因为有注意到JDK11其实也是一个长期支持的版本,而在项目重构的时候我们青睐他通过之前两个版本升级的垃圾回收器

[众所周知10和11都是对垃圾回收器做了优化,没做过测评听说拐一批(我自己服务器用的JDK9...也没怎么折腾过..吃灰吃了一年),9移除了8中被废弃的垃圾回收器配置组合,将原来默认Parallel修改成了G1,干掉了CMS垃圾回收器。10给了垃圾收集器的接口,重新划分了hotspot内部模块,将9中G1垃圾回收器的单线程标记扫描压缩算法改为并行GC的模式,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,从而达到拐一批的效果,也就是说GC更能抗了,你的代码可以再烂点了。]

可是Springboot1.5.X只支持到JDK10,虽然我们使用了最新能够支持JDK11的SpringBoot2.1.X(即Spring Framework 5.x),但是其实我们的诉求并不强烈(换句话说即没有遇到JDK8搞不定的东西,而遇到了SpringBoot版本低导致架构层的一些烦恼)所以最终愉快的使用了JDK8+SpringBoot2.1.x+SpringCloudGreenSR2(2019.9月的版本,我们十月的时候在线上用,现在想想就觉得离谱)

抛开我们自己(其实就是我和姜叔)的想法,整体版本的升高其实极大优化了一些东西,最常用的比如Optional的改进和优化,还有CompletableFuture API、Stream API、集合工厂方法这些的改进,其实这些基本带来了质变,由于8已经解决了问题,没有诉求大家也不会摆弄这些新玩意。好了,不说废话了,我们康康14到底改了啥:

这个是release Note:http://jdk.java.net/14/release-notes
这个是JAVA SE产品总监写的博客:https://blogs.oracle.com/java-platform-group/the-arrival-of-java-14
里面说有十六条主要改动(https://openjdk.java.net/projects/jdk/14/ ),我们一条一条看(吐槽)昂:

新功能

Pattern Matching for instanceof (Preview)

这条是有关关键字 instanceof的改动,大概意思是这样的:写了一个Function,但是由于可恶的泛型,这个方法有可能返回A类,也有可能返回B类。于是:

obj = func();
if (obj instanceof A) {
    A a = (A) obj;
    slove(a);
}

但是拥有了JDK14,你就可以:

obj = func();
if (obj instanceof A a) {
    slove(a);
} 

好炫酷的写法,我似乎从哪见过,哦好像是这个

if(k == (a.getValue() == null? 0:a.getValue()) ){}

啧啧啧,这真的是
UTOOLS1584789234653.png

Packaging Tool (Incubator)

打包工具??emmmm 因为学识疏浅,两年一直在web开发层混水,对于Java桌面的JavaFX了解不多,既然人说出了个打包工具(别真有人写微软桌面软件用java的..那您也太特立独行了,但是很难讲其他设备会不会使用这个东西,既然有更新,说明占有量还不错)

有了他你就不用借助第三方插件或者平台去打包你的Java程序了真的是
UTOOLS1584789674248.png

NUMA-Aware Memory Allocation for G1

针对G1垃圾回收器的 NUMA-Aware 内存分配,提高G1性能。啥?又改一版?之前一直有人吐槽说C的性能完爆Java,Go在多线程方面也是暴打Java,所以sun连续五个版本一直改,希望能变的越来愈好(虽然肯定是追不上),详细说下这个官方的说明:

现代的多核计算机越来越多地具有非统一的内存访问(NUMA),即内存与每个插槽或内核之间的距离并不相等。套接字之间的内存访问具有不同的性能特征,对更远的套接字的访问通常具有更多的延迟。由启用的并行收集器-XX:+UseParallelGC已经意识到NUMA多年了。这有助于提高跨多个套接字运行单个JVM的配置的性能。其他HotSpot收集器没有利用此功能,这意味着他们无法利用这种垂直多路NUMA缩放功能。大型企业应用程序尤其倾向于在多个套接字上以大型堆配置运行,但是它们希望在单个JVM中运行具有可管理性优势。使用G1收集器的用户越来越多地遇到这种扩展瓶颈。

对于此更详细的描述:

G1的堆组织为固定大小区域的集合。一个区域通常是一组物理页面,尽管使用(通过-XX:+UseLargePages)大页面时,多个区域可能组成一个物理页面。
如果+XX:+UseNUMA指定了该选项,则在初始化JVM时,区域将平均分布在可用NUMA节点的总数上。
在开始时固定每个区域的NUMA节点有些不灵活,但是可以通过以下增强来缓解。为了为mutator线程分配新的对象,G1可能需要分配一个新的区域。它将通过从NUMA节点中优先选择一个与当前线程绑定的空闲区域来执行此操作,以便将对象保留在年轻代中的同一NUMA节点上。如果在为变量分配区域的过程中,同一NUMA节点上没有空闲区域,则G1将触发垃圾回收。要评估的另一种想法是,从距离最近的NUMA节点开始,按距离顺序在其他NUMA节点中搜索自由区域。

oh天呢,这真的是
UTOOLS1584789674248.png

JFR Event Streaming

这个又是啥...看了文档的具体描述是暴露了JDK Flight Recorder 数据以进行连续监视。而HotSpot VM使用JFR发出500多个数据点,除了解析日志文件外,大多数其他方法无法使用。为了解决这个问题,在jdk.jfr当中用户可以直接从磁盘存储库读取记录数据或从磁盘存储流中读取数据,而无需转储记录文件。与流进行交互的方式是注册一个处理程序,例如lambda函数,以响应事件的到来而被调用。(真就万物都往响应式靠)

比如官方给了一个显示了总体CPU使用率和竞争超过10毫秒的锁的监控:

try (var rs = new RecordingStream()) {
  rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
  rs.enable("jdk.JavaMonitorEnter").withThreshold(Duration.ofMillis(10));
  rs.onEvent("jdk.CPULoad", event -> {
    System.out.println(event.getFloat("machineTotal"));
  });
  rs.onEvent("jdk.JavaMonitorEnter", event -> {
    System.out.println(event.getClass("monitorClass"));
  });
  rs.start();
}

而RecordingStream类实现了接口jdk.jfr.consumer.EventStream,该接口提供了一种统一的方式来过滤和使用事件,无论源是实时流还是磁盘上的文件。

public interface EventStream extends AutoCloseable {
  public static EventStream openRepository();
  public static EventStream openRepository(Path directory);
  public static EventStream openFile(Path file);

  void setStartTime(Instant startTime);
  void setEndTime(Instant endTime);
  void setOrdered(boolean ordered);
  void setReuse(boolean reuse);

  void onEvent(Consumer<RecordedEvent> handler);
  void onEvent(String eventName, Consumer<RecordedEvent handler);
  void onFlush(Runnable handler);
  void onClose(Runnable handler);
  void onError(Runnable handler);
  void remove(Object handler);

  void start();
  void startAsync();

  void awaitTermination();
  void awaitTermination(Duration duration);
  void close();
}

接下来是官方的描述:

该接口还可用于设置要缓冲的数据量,以及是否应按时间顺序对事件进行排序。为了最大程度地降低分配压力,还有一个选项可以控制是否应为每个事件分配新的事件对象,或者是否可以重用以前的对象。流可以在当前线程中启动,也可以异步启动。
Java虚拟机(JVM)每秒一次将线程本地缓冲区中存储的事件定期刷新到磁盘存储库。一个单独的线程解析最近的文件,直到写入数据为止,然后将事件推送给订阅者。为了保持较低的开销,仅从文件中读取活动订阅的事件。要在刷新完成后收到通知,可以使用EventStream :: onFlush(Runnable)方法注册处理程序。这是在JVM准备下一组事件时将数据聚合或推送到外部系统的机会。

但是这个功能的大部分都还在测试中:

所以官方提出了备用方案:
JMX..JMX通知为JDK和第三方应用程序提供了一种公开信息以进行持续监视的方法。

运维可以深究下这个,因为可能会对监控以及排查问题帮助比较大(问题离谱到需要排查JVM了,那这个错也犯的挺牛逼),唉
UTOOLS1584790783477.gif

Non-Volatile Mapped Byte Buffers

哦天呢,居然是新的特定于JDK的文件映射模式,可以通过FileChannel使用API创建MappedByteBuffer引用非易失性存储器的实例。(说人话就是加了新的类,支持更多的读取方式),不过可惜的是我又是对io下的包不是特别熟悉,对于MappedByteBuffer又了解的不是很多(他喵的他更新的东西都不是了解的很多),这里有人详细介绍这个:https://www.jianshu.com/p/f90866dcbffc

Helpful NullPointerExceptions

哈哈哈哈,这个我喜欢,毕竟空指针是缠绕大家最多的一个问题,而每次都能找到对应的行,但是望着行里十几个变量被get的时候,其实也很难判断到底空指针出在哪里,不用怕:
NullPointerException通过精确描述哪个变量来提高JVM生成的s 的可用性null。JDK14解决了这个问题,那么我要在日志看到需要干什么呢?需要在vm参数增加:

XX:+ShowCodeDetailsInExceptionMessages

这样你就可以看到哪一行,是谁因为什么报了空指针,对于这个功能:
UTOOLS1584791343372.png

Records (Preview)

这是一个预览版的功能,所以你编译执行都是用不了的..(勾引你?)

其实是这样子的,JDK产品看不惯你搞不懂Lombok还要硬用的样子,也受不了团队因为Lombok而大打出手的样子,Lombok由于一些使用者对于@Data注解的作用的不理解,导致各种奇怪问题产生,而最终黑锅还是人家一手扛了。所以JDK产品经理直接想办法:

/**
* 你声明一个数据类
* /
public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x &&
                y == point.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

在使用了Lombok之后:

@Data
public class Point {
    private final int x;
    private final int y;
}

当你使用record的时候:

public record Point(int x, int y) { }

ohh~ 饿妹子嘤,这个东西我又好像见过,某jvm语言的data class 就是这样子的!(我没有暗示kotlin )

当然这个东西也不是让你白嫖的,拥有诸多限制:

Records cannot extend any other class, and cannot declare instance fields other than the private final fields which correspond to components of the state description. Any other fields which are declared must be static. These restrictions ensure that the state description alone defines the representation.

Records are implicitly final, and cannot be abstract. These restrictions emphasize that the API of a record is defined solely by its state description, and cannot be enhanced later by another class or record.

啥?不能有继承父类的情况出现,字段必须是final 修饰了(所以上面的EG都写了final),怎么说呢这个...挺鸡肋的,你说他不行吧还挺简单方便的,你说他行吧,final和extend的禁用坏了事...但是作为预览版本,这丝毫不影响我吹一波:
UTOOLS1584885453598.png

Switch Expressions (Standard)

我似乎走错地方了。我记得JDK12就有对Switch的改写,但是这次更加彻底了:
JDK12将switch改为支持表达式了:

static void howMany(int k) {
    switch (k) {
        case 1  -> System.out.println("one");
        case 2  -> System.out.println("two");
        default -> System.out.println("many");
    }
}

但是这次这样子了:

static void howMany(int k) {
    System.out.println(
        switch (k) {
            case  1 -> "one";
            case  2 -> "two";
            default -> "many";
        }
    );
}

哦谢特,你居然优化了自己的写法,真是不可思议。不光如此,还有船新的关键字:

int result = switch (s) {
    case "Foo": 
        yield 1;
    case "Bar":
        yield 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        yield 0;
};

这时候可能有长的帅的网友要问了:这yield 和return/break有啥区别阿。你还没看出来吗,人yield是对switch的终结,比如你在for里套switch:

for (int i = 0; i < MAX_VALUE; ++i) {
        int k = switch (e) { 
            case 0:  
                yield 1;
            case 1:
                yield 2;
            default: 
                continue z; 
                // ERROR! Illegal jump through a switch expression 
        };
    ...
}

显然这个continue是出不了switch的...这个功能那真的是
UTOOLS1584789674248.png

Deprecate the Solaris and SPARC Ports

哦,终于迎来了第一个写在更新内容中的弃用功能了。不过.....我好像又tm没用过
看下描述:

An attempt to configure a Solaris and/or SPARC build will produce the following output:

$ bash ./configure
...
checking compilation type... native
configure: error: The Solaris and SPARC ports are deprecated and may be removed in a future release. \
Use --enable-deprecated-ports=yes to suppress this error.
configure exiting with result code 1

The new build-configuration option --enable-deprecated-ports=yes will suppress the error and continue:

$ bash ./configure --enable-deprecated-ports=yes
...
checking compilation type... native
configure: WARNING: The Solaris and SPARC ports are deprecated and may be removed in a future release.
...
Build performance summary:
* Cores to use:   32
* Memory limit:   96601 MB
The following warnings were produced. Repeated here for convenience:
WARNING: The Solaris and SPARC ports are deprecated and may be removed in a future release.
$

哦。这波阿,这波是弃用 Solaris 和 SPARC 端口,我也不知道干啥的,大概懂linux比较多的大佬清楚他在干嘛吧。我反正是不知道,但是这不妨碍我喊nb。
UTOOLS1584886540048.png

Remove the Concurrent Mark Sweep (CMS) Garbage Collector

惊了惊了,CMS被愉悦送走了,在大力支持G1的情况下CMS终究还是被删了代码,真实,真的是太恐怖了:

This change will disable compilation of CMS, remove the contents of the gc/cms directory in the source tree, and remove options that pertain solely to CMS.

真就连根拔起不留情面,真的是
UTOOLS1584886740047.png

ZGC on macOS

将ZGC垃圾回收器移植到MacOS上...啥,宁不是服务linux,特性多平台就真服务呗,同样的还有:

ZGC on Windows

虽然Linux是亲爹,但还是象征性移植,你可以加vm参数

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

这个太敷衍了。。
UTOOLS1584886540048.png

Deprecate the ParallelScavenge + SerialOld GC Combination

废弃了parallel young generation GC与SerialOld GC的组合...还是关于GC的优化,这种优化看看就好,反正你也不会用,如果说你会用了,当使用:

-XX:+UseParallelGC -XX:-UseParallelOldGC
或者
-XX:-UseParallelOldGC
都会warning:OpenJDK 64-Bit Server VM warning: Option UseParallelOldGC was deprecated in version 14.0 and will likely be removed in a future release.

oh天呢,这真的是
UTOOLS1584789674248.png

Remove the Pack200 Tools and API

删除Pack200工具和API...啥,好像又没用过。但官网有说为啥要删除:

删除Pack200的三个原因:
- 从历史上看,通过56k调制解调器缓慢下载JDK阻碍了Java的采用。JDK功能的不断增长导致下载量膨胀,进一步阻碍了采用。使用Pack200压缩JDK是缓解此问题的一种方法。但是,时间已经过去了:下载速度有所提高,并且JDK 9引入了针对Java运行时(JEP 220)和用于构建运行时的模块(JMOD)的新压缩方案。因此,JDK 9和更高版本不依赖Pack200。JDK 8是pack200在构建时使用压缩的最新版本,unpack200在安装时未使用压缩的最新版本。总之,Pack200的主要使用者(JDK本身)不再需要它。
- 除了JDK,Pack200还可以压缩客户端应用程序,尤其是applet。某些部署技术(例如Oracle的浏览器插件)会自动解压缩applet JAR。但是,客户端应用程序的格局已经改变,并且大多数浏览器都放弃了对插件的支持。因此,Pack200的主要消费者类别(在浏览器中运行的小程序)不再是将Pack200包含在JDK中的驱动程序。
- Pack200是一项复杂而精致的技术。它的文件格式与类文件格式和JAR文件格式紧密相关,二者均以JSR 200无法预见的方式演变。(例如,JEP 309向类文件格式添加了一种新的常量池条目,并且JEP 238在JAR文件格式中添加了版本控制元数据。)JDK中的实现是在Java和本机代码之间划分的,这使得维护变得很困难。该API中的API java.util.jar.Pack200不利于Java SE平台的模块化,导致在Java SE 9中删除了其四种方法。总体而言,维护Pack200的成本是巨大的,并且超过了将其包含在Java SE和JDK中的好处。

删除的范围有:

java.util.jar.Pack200
java.util.jar.Pack200.Packer
java.util.jar.Pack200.Unpacker

哦,还真能吹,复杂又精致的技术那你删个锤子,复杂不代表有用啊大哥,这删除我只能说:
UTOOLS1584789234653.png

Text Blocks (Second Preview)

啥,您终于支持了!!!!不过支持了我也用不着...
具体就是这个样子:

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

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

u1s1,不管是python还是markdown,大家都挺好的,怎么到了你这就拉了垮(多年前JDK的var 被人js吐槽的,至今耿耿于怀)
这个东西,必须:
UTOOLS1584886740047.png

Foreign-Memory Access API (Incubator)

提供了API来操作堆外内存,给了程序员更大的自由度,给了你机会相应的就带来新的风险,真不会有人操作堆外内存宕机吧(???ByteBuffer表示哥很稳定,有手就行不会出错)
官方提供了一些代码来演示是怎么玩的:

SequenceLayout intArrayLayout
    = MemoryLayout.ofSequence(25,
        MemoryLayout.ofValueBits(32,
            ByteOrder.nativeOrder()));

VarHandle intElemHandle
    = intArrayLayout.varHandle(int.class,
        PathElement.sequenceElement());

try (MemorySegment segment = MemorySegment.allocateNative(intArrayLayout)) {
    MemoryAddress base = segment.baseAddress();
    for (int i = 0; i < intArrayLayout.elementCount().getAsLong(); i++) {
        intElemHandle.set(base, (long) i, i);
    }
}

毕竟我没安装,所以代码从官方抄的,你要是没看懂建议去文档,他说的很详细。我觉得这个平时用不着(毕竟业务码农没机会),但是这不妨碍我喊:
UTOOLS1584789234653.png

删除内容(除了在fast review提及的)

这里我不介绍了,毕竟吐槽新功能,所以想知道人删除啥了,去这里:http://jdk.java.net/14/release-notes#removed

总结

可能一时觉得有点吃撑,但是这些新功能就当是拓宽视野了,毕竟JDK整的Nio/优化/Stream API/ForkJoin都是处在瘸腿观望的时候,语言的特性只是为了解决问题,写什么不重要,我觉得以后出现更替的契机便是硬件技术的飞速提升导致原有的软件和特性无法满足高速/及时/低错误,所以更新是为了追求性能和解决更多问题,而不是我们需要更新版本,那就更新吧。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注