JAVA13新鲜特性简述

本文是oracle最新发布的java13新特性一览,不包含被deprecated的特性,以及与安全,代码集等有关的内容.

1.nio新api:

类:FileSystems

newFileSystem(Path)
newFileSystem(Path, Map<String, ?>)
newFileSystem(Path, Map<String, ?>, ClassLoader)

粗略看了一下描述,其实直接看这个参数最全的方法:

public static FileSystem newFileSystem​(Path path, Map<String,​?> env, ClassLoader loader) throws IOException

官方的描述是尝试去构建一个FileSystem, 而它会按一个文件系统的方式去访问指定的文件.

该方法会使用一些掀起定的provider,这些provider会创建一些假的文件,并用一至多个文件的内容表示一个文件系统.

方法自身会迭代所有安装的provider(看起来慒逼的读者稍等),并依次执行每个provider的newFileSystem(Path,Map)方法,如果有一个provider返回了一个文件系统,那么迭储藏室终止并返回该文件系统.如果没有任何一个安装的provider返回FileSystem,那么将尝试使用参数中的class loader去定位provider,如果成功,则停止迭代并返回file system.

所谓”安装的provider”,它其实是FileSystemProvider抽象类,字面意思很明显,从jdk7就已经出现了,而所谓”安装”,其实是依赖spi机制,就是正常的spi用法,没什么可解释的.

很巧作者之前没了解过FileSystemProvider这个鬼,正好借机了解了,看注释就是将若干文件的内容按照文件系统解释,仿佛自己实现了一套文件系统,有一点虚拟化的味道.是不是想到了zookeeper?

参数path是文件路径,map则是配置文件系统的属性集合,可空.

其实这个api就是相当于一个封装,我们使用spi机制安装了若干个FileSystemProvider,然后使用FileSystems的newFileSystem就可以获取到provider实现的FileSystem.

2.nio新api:

类ByteBuffer,新增若干对于buffer的批量的数据get/put,而且不影响buffer位.

不了解nio的同学可能有些难以理解,其实要理解也容易,打开jdk8的ByteBuffer源码,直接找到get或者put方法,要参数最长的那个,把注释看一遍就理解了.

jdk8中现有的示例:

public ByteBuffer put(byte[] src, int offset, int length);
public ByteBuffer get(byte[] dst, int offset, int length);

注意这两个方法,get方法的get操作其实是一个复合操作,它会将当前buffer的数据从position开始,拷贝指定的长度length到目标dst,同时更新自身的position增加length,当然,超长的情况下会抛出异常并且不执行拷贝和更新position.

put操作同样,也会将src中从offset开始的length个元素放置入buffer,并更新position增加length.

在java8中,多线程进行批量的读写处理要如何进行呢?

通过slice方法获取一个从当前position开始的buffer切面,共享这一段数据,但postion,mark和limit独立;或使用duplicate方法,共享完整的数据,同样保持position,mark和limit的独立,各线程各玩个的,而position,mark和limit等信息是不对外暴露的.

从某种角度理解,在8中多线程操作同一个buffer的这种使用场景,虽然是共同操作了同一段底层数据,实际上是多个buffer对象,互相对对方的读写没有和mark,limit,position有关的影响,所以各个线程可以依旧从自己持有的新buffer引用(共同底层数据,不同的buffer对象)的position位置去读写.所以其实并非完全是同一个buffer在多线程下共享.

因此,从jdk13开始支持了这样的场景:多个线程同时对一个buffer进行批量读写,且不创建新的buffer(slice或者duplicate),并且这个操作必然能并发,那么这些可并发的操作就不能去更新相应的position,若有其他操作依托于position进行,在保持了可见性的前提下,依旧能从buffer的position位进行读写.

所以,你会在java13的ByteBuffer api上看见四个这样的方法:

public ByteBuffer get(int index, byte[] dst);
public ByteBuffer get(int index, byte[] dst, int offset, int length);
public ByteBuffer put(int index, byte[] src);
public ByteBuffer put(int index, byte[] src, int offset, int length);

注意第一个参数index,代表从buffer的数据的哪个索引开始,它绝对不会改变position,也没有创建新的buffer.

在ByteBuffer 的子类(官方8提供)中,MappedByteBuffer 是个抽象类,没有实现slice或者duplicate方法,如果自己去实现,很可能会保持大量的原buffer的引用.可想而知,13中推出的四个新api可以令我们在多线程环境下减少无用引用的创建.

3.unicode12.1 支持.

最近每个版本都会扩大字符集支持,略.

4.zgc更新,支持配置返还无用内存,最大堆内存支持16T,软最大内存设置.

在前面的java9-12一文中,曾介绍G1为了云友好,支持在空闲时返还无用内存,从而帮助用户错峰节省了内存资源占用.zgc在11出现,12支持了并发类卸载,但并不支持内存的无用返还,目前在13中已经支持.这一特性可以使用 -XX:-ZUncommit来禁用,默认是开启的,同时,如果内存占用已达到配置的最低内存,那么即使内存空闲也不会返还,所以若显式将初始内存和最大内存配置一致,等于隐式禁用了这一特性.

zgc可以使用 -XX:ZUncommitDelay=<seconds> 决定多少秒算空闲,默认为300秒.

此外,在jdk13中,官方为hotspot虚拟机新增了一个选项:-XXSoftMaxHeapSize.但目前只有zgc实现了对它的支持,其他gc并不生效,即开启-XX:+UseZGC.

当我们使用该选项设置了一个软的最大堆内存大小后,gc将争取堆内存不超过指定的大小,除非gc最终认定这样做是避免oom必须的.软最大堆内存大小不可超过-Xms指定的值,如果我们没有在命令行中设置它,默认的值就是最大堆内存大小.

该值是动态可调的,在运行时,可以使用jcmd VM.set_flag SoftMaxHeapSize <bytes>或者通过HotSpot MXBean设置.

在一些特定的场景下我们需要这个新的特性,如非常在意资源使用,希望保持低堆内存占用,但同时又想同时能应付临时的,偶发的内存空间突增,尤其是在并发场景下无法预知的对象分配速率的突增.设置软最大堆内存将鼓励gc维护一个小堆,这样gc会更积极的进行垃圾回收,对于应用程序突发地增加对象分配速度也能更加从容应对.

5.动态cds归档.

详情参考JEP350,这一特性支持了在java进程退出时动态的进行类信息的归档,从而避免了用户需要烦琐地对每个进程手动归档.在前面java9-12一文中,已经介绍了近几个版本对于类数据共享的新支持,这一个算是续集吧.

6.语法糖.(预览版)

在前面JAVA9-12一文中,已经介绍过switch支持了新的case a ->表达式的语法糖.

在jdk13,支持了更加丰富的扩展,说起来费劲,总之官方的两段示例代码很好理解:

String formatted = 
    switch (obj) {
        case Integer i -> String.format("int %d", i)
        case Byte b    -> String.format("byte %d", b);
        case Long l    -> String.format("long %d", l); 
        case Double d  -> String.format("double %f", d); 
        case String s  -> String.format("String %s", s); 
        default        -> obj.toString();
    };
int eval(Node n) {
    switch(n) {
        case IntNode(int i): return i;
        case NegNode(Node n): return -eval(n);
        case AddNode(Node left, Node right): return eval(left) + eval(right);
        case MulNode(Node left, Node right): return eval(left) * eval(right);
        default: throw new IllegalStateException(n);
    };
}

同时,jdk13提出了一个”文本块”的概念,

其实就是支持了字符串(文档)的多行语法,一样说起来费力,看起来挺像python的.

从前:

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

现在:

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`;
               """;

使用该表达式的字符串对象兼容老的表达方式,如两者混合使用,进行相加等操作,调用字符串String的方法等.

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

Stirng新增的有关方法:

String::stripIndent(): 从文本块语法的表达中去除空格.
String::translateEscapes(): 转义退出字符序列.
String::formatted(Object... args): 进行文本块中某些值的格式化.

7.与dom sax解析有关的新api,我也不怎么熟悉,简单说一下吧.

官方的说法是为初始化dom sax工厂时提供了创建带有默认命名空间的api,特点是方法名上带有NS,三个方法分别是:

newDefaultNSInstance()
newNSInstance()
newNSInstance(String factoryClassName, ClassLoader classLoader)

相应的,使用jdk13,这段代码:

DocumentBuilder db = DocumentBuilderFactory.newDefaultNSInstance().newDocumentBuilder();

等效于:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance(); 
dbf.setNamespaceAware(true); 
DocumentBuilder db = dbf.newDocumentBuilder();

8.StringBuilder和StringBuffer参数为负长度数组时抛出NegativeArraySizeException .

9.linux系统下,进程的默认开启机制使用posix_spawn,这一块需要参考linux创建进程的四种方式,fork(), fork()-exec(), posix_spawn()是异步进程,多进程可以并行执行,和system这种同步方式,多进程不能同时执行.从官方给的链接来看,原来用的似乎是fork/vfork?

10.jli包限定使用方法句柄对于静态常量字段的设值权限.即使使用了Field.setAccessibe(true)设置了权限,那么对相应的字段进行java.lang.invoke.MethodHandles.Lookup::unreflectSetter 时,也会抛出IllegalAccessException .在前面java9-12一文中提到过一些关于两个句柄的介绍,作者个人建议了解,个人觉得它们有未来在各框架中大量使用的可能性.

10.SocketImpl一些默认实现的变更.

此处影响的三个方法是jdk9才出现的方法,在13中,它的默认返回值会是一个空集,getOption和setOption将默认抛出UnsupportedOperationException,代码中或继承了SocketImpl或DatagramSocketImpl 则必须重写这两个方法.

11.NewDirectByteBuffer 开辟的直接内存将固定为大字节序.

  1. Files.isHidden 在windows中使用隐藏目录时返回true.

13.Base64.Encoder在编码解码时若不能申请内存成功,将抛出oom而不是NegativeArraySizeException.

14.对于压缩文件(zip或jar)的api进行内容更新时的注意事项,若使用了zip file system去更新其中包含未压缩项的压缩包,则会令损坏该文件,若该包中不包含未压缩的项,则不会有问题.当要对这种含有未压缩条目的压缩包更新时,应使用jar工具或者java.util.zip包下的工具解决.

15.对于x86_64无压缩引用的场景,将有更多可用寄存器资源.

在x86_64上有压缩引用的场景下运行时,会消耗一个cpu寄存器去存放base指针,它将用来处理引用的编解码,那么这个寄存器就不能用来分配了.在13之前,即使没用到压缩引用,这个寄存器也是无用了,在本版本,该寄存器会被交还给寄存器池,使用了超大堆和有XX:-UseCompressedOops配置的场景将因此获益.

16.提升稀疏prt条目工效.

prt即per region table,与垃圾收集器中的位图有关,在g1中,记忆集的存储便是稀疏prt,官方的简述很好理解,原来prt中能存放的条目是随着region的增大而线性增长的,现在变为指数增长.

这意味着G1对于 1/2/4/8/16/32 MB 的region将能每prt分别使用 4/8/16/32/62/128 个条目,以前则分别是每prt使用4/8/12/16/20/24 条目.

17.jrt协议只能编码jrt:/modules树下的路径.前面说过,从jdk9开始,官方提供了一个新的url pattern jrt,并使用jrt:/module/resource具体定位一个资源(类或配置等).在/packages树中的文件,使用jrt 文件系统进行toUri操作时,可能会抛出IOError,阻止下一步操作.

    原文作者:山人
    原文地址: https://segmentfault.com/a/1190000020438868
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞