Java10概述
2018年3月,我们看到了Java10的最新半年版。
在本文中,我们将研究此版本中引入的重大更改,并讨论一些较小的改进,这些改进将使开发人员和操作人员的生活更轻松。
Java10:巨大的变化
Java 10中的两大故事是:
- 新的
var
关键字,就像你想象的任何一种新的语言结构一样 - 新的六个月发布周期
此外,开发人员将很高兴看到更多的API发展。此外,还有运行时改进、新的性能调优旋钮,以及我们在每个版本中所期望的垃圾收集改进。
但是还有很多其他有趣的事情,特别是如果你知道如何在字里行间阅读并展望9月份的Java11。
局部变量类型推断
除了java1.4 days中的assert
之外,新的关键字似乎总是会引起很大的轰动,var
也不例外。
也许,最奇怪的是,它实际上不是一个保留字,而是一个完全不同的词。但是,稍后会有更多的内容。
var
关键字的作用是将局部变量赋值。。。
HashMap<String, String> ughThisIsSoVerbose = new HashMap<>();
分为:
var succinct = new HashMap<String, String>();
增加可读性
简单地说,只要右侧的构造不需要左侧的目标类型(像lambdas那样),就可以使各种代码更易于阅读,如下所示:
var tshirts = Lists.of("Baeldung Medium", "Java Large", "Lua Small");
var lines = Files.get(Paths.get("log/catalina.out"));
var length = lines.count();
一些注意事项
换句话说,Java10将局部变量类型推断引入到语言中。在编译时,它根据值类型计算出引用类型。
现在,我们可以把它添加到Java所做的不断增长的类型推断列表中,已经包括了泛型和lambda
表达式的类型推断。
这一特点由来已久。早在2001年就有人提出了这一建议,当时Gilad Bracha发表了以下评论。
人类从两个方面受益于类型声明的冗余。首先,冗余类型充当有价值的文档-读者不必搜索getMap()的声明来找出它返回的类型。第二,冗余允许程序员声明所需的类型,从而受益于编译器执行的交叉检查。
不过,时代变了,Java语言正在学习选择的好处。例如,在有些情况下,vars
增加了简洁性,可能会使代码更难阅读:
var x = someFunction();
上面的代码片段是完全有效的Java10代码,阅读起来非常混乱。
这很让人困惑,因为读者不可能在不追踪某个函数的返回类型的情况下说出x
的类型。多年来,针对动态类型语言的类似投诉一直不断。
当然,这种特殊用法正是吉拉德15年前警告社区的。
所以,小心使用var
,记住目标是编写可读的代码。
其实不是保留字
别让别人告诉你这是个保留词。在Java中,var
是一种特殊的新类型。
所以,实际上,您仍然可以在代码的其他地方使用var
,比如作为变量或类名。这使得Java能够与java10之前的代码保持向后兼容,这些代码可能已经做出了有趣的选择,即命名一个变量或两个“var
”
而且,故事还有很多!在Oracle的局部变量类型推断指南中,阅读将var
用于非可表示类型,以及var
在多态性和lambda
表达式方面的限制。
不可修改的集合增强功能
要介绍下一个增强功能,请考虑下面的Java拼图器。本课程结束时v
值是多少?
var vegetables = new ArrayList<>(Lists.of("Brocolli", "Celery", "Carrot"));
var unmodifiable = Collections.unmodifiableList(vegetables);
vegetables.set(0, "Radish");
var v = unmodifiable.get(0);
答案当然是Radish
。但是,难道不是不可改变的,嗯,不可改变的吗?
不可修改与不可修改视图
实际上,根据Java 10的更新集合Javadoc,unmodifiableList
返回一个不可修改的视图集合:
不可修改的视图集合是不可修改的集合,也是备份集合上的视图。
不可修改视图集合的示例是Collections.unmodifiableCollection
集合, Collections.unmodifiableList
,以及相关方法。
请注意,对备份集合的更改仍然是可能的,如果发生更改,则可以通过不可修改的视图看到这些更改。
但是,假设你想要一些真正不可修改的东西,你会怎么做?
真正不可修改的方法能站出来吗?
好吧,Java10添加了两个新的API来实现这一点,也就是说,创建完全不能修改的集合。
第一个API允许通过添加copyOf
:
var unmodifiable = List.copyOf(vegetables);
这与将列表包装在Collections.unmodifiableList
. copyOf
按迭代顺序执行浅拷贝。现在vegetables
的变化不会表现为不可改变。然而,他们与我们原来的方法。
第二个API向Streampackage
中的Collectors
类添加了三个新方法。现在,可以使用toUnmodifiableList
、toUnmodifiableSet
和toUnmodifiableMap
直接流式传输到不可修改的集合中:
var result = Arrays.asList(1, 2, 3, 4)
.stream()
.collect(Collectors.toUnmodifiableList());
请注意,虽然这些方法名称可能会提醒您Collections.unmodifiableList
诸如此类,这些新方法产生真正不可修改的列表,而Collections.unmodifiableList
返回不可修改的视图。
G1GC改进
Java9将垃圾第一垃圾收集器(G1GC)设为默认值,取代了并发标记扫描垃圾收集器(CMS)。Java10为G1GC引入了性能改进。
在java10中,G1GC在完全GC期间引入了完全并行处理,从而提高了性能。此更改不会帮助垃圾收集器获得最佳情况下的性能,但它确实显著减少了最坏情况下的延迟。这使得垃圾收集的暂停对应用程序性能的压力要小得多。
当并发垃圾收集落后时,它会触发一个完整的GC收集。性能改进修改了完全收集,使其不再是单线程的,这大大减少了执行完全垃圾收集所需的时间。
应用程序类数据共享
Java5引入了类数据共享(CDS),以缩短小型Java应用程序的启动时间。
一般的想法是,当JVM第一次启动时,bootstrap
类加载器加载的任何内容都被序列化并存储在磁盘上的一个文件中,在JVM的未来启动时可以重新加载该文件。这意味着JVM的多个实例共享了类元数据,因此不必每次都加载它们。
共享数据缓存意味着小型应用程序的启动时间大大缩短,因为在这种情况下,核心类的相对大小大于应用程序本身。
Java10对此进行了扩展,包括系统类加载器和平台类加载器。要利用这一点,只需添加以下参数:
-XX:+UseAppCDS
将自己的类添加到存档中
但是,更大的变化是,它允许您将自己的特定于应用程序的类存储到类数据共享缓存中,这可能会减少启动时间。
基本上,这是一个三步走的过程。第一步是创建应存档的类的列表,方法是使用适当的标志启动应用程序并指示要将列表存储的位置:
java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=myapp.lst \
-cp $CLASSPATH $MAIN_CLASS
然后,使用此列表,您将创建一个CD存档:
java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=myapp.lst \
-XX:SharedArchiveFile=myapp.jsa \
-cp $CLASSPATH
最后,使用存档运行应用程序:
java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
-cp $CLASSPATH $MAIN_CLASS
新的实时编译器
实时(JIT)编译器是Java的一部分,它在运行时将Java字节码转换为机器码。原来的JIT编译器是用C++编写的,现在被认为是很难修改的。
Java9引入了一个新的实验接口,称为JVM编译器接口或JVMCI。新接口的设计使得用纯Java重写JIT编译器成为可能。Graal是最终的JIT编译器,完全用Java编写。
Graal目前是一个实验性的JIT编译器。只有Linux/x64机器才能使用它,直到Java的未来版本。要启用Graal,请在启动应用程序时将以下标志添加到命令行参数:
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
而且,请记住,Graal团队在第一个版本中没有承诺这个编译器会更快。主要的希望是,Graal将帮助JVMCI的发展,并使未来的维护变得容易处理。
线程本地握手
在JVM的性能改进中,有一个微妙但强大的改进,称为线程本地握手(Thread-Local Handshakes
)。
在可维护性操作期间,如收集所有线程的堆栈跟踪或执行垃圾收集,当JVM需要暂停一个线程时,它需要停止所有线程。有时,这些被称为“停止世界”暂停。这是因为JVM希望创建一个全局安全点,一旦JVM完成,所有应用程序线程都可以从这个安全点重新开始。
不过,在Java10中,JVM可以将任意数量的线程放入一个安全点,并且线程可以在执行规定的“握手”后继续运行。这导致JVM一次只能暂停一个线程,而在此之前,它必须暂停所有线程。
很明显,这不是一个开发者可以直接使用的特性,但是每个人都会喜欢。
GC变革的先行者
而且,如果您密切关注的话,您还将看到这与Java11中即将推出的(也是实验性的)低延迟垃圾收集器有关,它的GC时钟仅为10ms,它也是Java11中非常酷的no-GC选项的近亲。
集装箱意识
JVM现在知道它何时在Docker容器中运行。这意味着应用程序现在拥有关于docker容器分配给内存、CPU和其他系统资源的准确信息。
以前,JVM查询主机操作系统以获取此信息。当docker容器实际想要公布不同的资源集时,这会导致一个问题。
例如,假设您想创建一个基于Java的docker映像,其中运行的JVM被分配了容器指定的25%的可用内存。在内存为2G的盒子上,运行为0.5G内存配置的容器时,Java9和更早的版本会根据2G数字而不是0.5G错误地计算Java进程的堆大小。
但是,现在,在Java10中,JVM能够从容器控制组(cgroups)中查找这些信息,Docker将这些细节放在这里。
有一些命令行选项可以指定Docker容器中的JVM如何分配内部内存。例如,要将内存堆设置为容器组大小并限制处理器的数量,可以传入以下参数:
-XX:+UseCGroupMemoryLimitForHeap -XX:ActiveProcessorCount=2
随着容器成为部署服务的标准方式,这意味着开发人员现在有了一种基于容器的方式来控制他们的Java应用程序如何使用资源。
备用内存分配
通过允许用户指定替代内存设备来分配堆,Java正在向更异构的内存系统发展。
一个即时用例是能够在非易失性DIMM(NVDIMM)模块上分配堆,这通常用于大数据应用程序。
另一个用例是许多JVM进程在同一台机器上运行。在这种情况下,最好将需要较低读取延迟的进程映射到DRAM,将其余进程映射到NVDIMM。
要使用它,请将此标志添加到启动参数:
-XX:AllocateHeapAt=<path>
对于此参数,path
路径通常是内存映射的目录。
使用OpenJDK简化SSL
java10的开源版本OpenJDK也收到了一些关于根证书的好消息。Java附带了一个名为cacerts的密钥存储区,它是JVM可以用来执行SSL握手之类的证书颁发机构的根证书的所在地。但是,在OpenJDK中,这个密钥存储总是空的,依赖于用户来填充它。
如果您的应用程序需要打开SSL套接字,那么这种额外的维护使得OpenJDK不那么吸引人。然而,Oracle在这个版本中决定将由Oracle的javase根CA程序颁发的根证书开源,以便现在可以将它们包含在JDK的开源版本中。基本上,这意味着使用OpenJDK做一些简单的事情,比如在应用程序和google restful服务之间通过HTTPS进行通信,将会简单得多。
通过使用keytool列出cacerts中的证书,您可以随意查看差异:
keytool -cacerts -list
如果您使用的是openjdk9或更早版本,它将是空的,但是对于openjdk10,它将与来自Digicert、Comodo、Docusign和许多其他人的证书保持一致。
新的发布周期
除了项目管理机制之外,Java10实际上还改变了类文件中的版本编号模式。
你们以前都见过这样的例外:
Unsupported major.minor version 52.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
...
当然,当您收到这个异常时,如果您一直跟踪,您就知道这意味着您试图在Java7JVM或更早版本上运行Java8库,因为52.0
意味着Java8,就像51.0
意味着Java7一样。
不过,现在新的编号系统有了语义含义。它看起来像这样:
$FEATURE.$INTERIM.$UPDATE.$PATCH
FEATURE是指Java的版本。所以,在Java10中,特性是10。它将每六个月增加一次,与新的Java发布周期相匹配。
INTERIM过渡期实际上是为未来的“过渡”周期保留的。例如,如果Java想以每六个月一次的速度开始发布。就目前而言,它将永远是0。
UPDATE更新有点奇怪。它从0开始,在最后一个特性发布后一个月,它将增加到1。之后,每三个月增加一次。所以,这意味着Java10在2018年4月的更新是1。2018年7月,它是2,9月,它是3,一直递增,直到Java10是EOL。
PATCH补丁是任何需要在更新增量之间进行的版本,例如,关键的bug修复。
此外,版本号会删除后面的零。所以,这意味着Java10上线时的版本字符串仅仅是10。
4月份,Oracle发布了10.0.1,7月份,它发布了10.0.2。你可以在他们的网站上查看这两个版本的发行说明。
其他改进
java10版本包括额外的错误修复和性能改进。最大的性能提升是在JShell REPL工具的启动时间。这将使使用该工具更具响应性。
结论
java10是JDK的第一个新版本,新的发布周期为6个月。
从现在起,每一个版本都会有更少的大型特性,但它们会更快。这意味着,如果一个主要功能错过了一个版本,它很可能只会在6个月后发布。最初的发布周期可能会将一个新特性推出几年。
这次,发布的一些主要特性是并行化的垃圾收集、局部变量类型推断和新的发布周期编号模式。最后,要了解更多细节,请查看正式的Java10发行说明。