3年前 (2021-02-17)  Java系列 |   抢沙发  235 
文章评分 0 次,平均分 0.0

Java10有什么新功能?

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类添加了三个新方法。现在,可以使用toUnmodifiableListtoUnmodifiableSettoUnmodifiableMap直接流式传输到不可修改的集合中:

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发行说明。

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/1532.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册