本文是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 开辟的直接内存将固定为大字节序.
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,阻止下一步操作.