文档结构  
原作者:未知 (2016-11-02)    来源:SitePoint [英文]
CY2    计算机    2016-11-10    0评/619阅
翻译进度:已翻译   参与翻译: tony (19), 薯片番茄 (12)

两个月前我着带你 深入Java 9 ,看着新的语言特性和API。但是有很多事情导致我不得不离开,所以分两部分来完成它。在第一部分我们会看新版本字符串和命令行语法,多版本的JARs,改进的日志和更多其他。

JShell

在第一部分我们跳过Jigsaw不讲,因为已经有许多人在讨论这个话题。在这部分,我们将对JShell做些类似的事情,这是java全新的REPL。(如:是一个地方输入java代码,它能立即给你进行计算)。如果你对此并不了解(或了解的不是很清楚),你可以阅读详细介绍来了解更多内容,这是Robert Field去年在比利时的Devoxx所著。(JEP 222)

第 1 段(可获 1.65 积分)

新的版本字符串

为了更轻松地入门,我们从一些简单的介绍开始:版本字符串。

我试图去了解Java版本的命名规则。从最初的JDKs 1.0和1.1版开始——这两个版本都还算不错,但是这之后的版本就开始走下坡路。能明显看出的是,从1.2到1.5版本,这段时间它们更名为Java2(还记得J2SE么,这里的2就是指代Java2)。到JDK1.5版本时,缺点更是显而易见,所以Sun公司 开始将他命名为 Java 5。大约在Java6时,整个Java2的想法默默的被埋没,不过这样一来,事情反而开始变的明朗了一些--我们简单的称它为“Java X”吧。(你知道吗?,所有的Java版本,包括Java7在内都有一个很酷的工程名。如,TigerMustang

第 2 段(可获 1.73 积分)

在JVM 报告的版本字符串并没有做出修改----它们一直是1.x....这样的格式,而如今在 JEP 223 中, 版本字符串和命名模式一致。如果检查相关的系统属性(见示例), 输出会是下面这样的内容:

java.version: 9-ea
java.runtime.version: 9-ea+138-jigsaw-nightly-h5561-20161003
java.vm.version: 9-ea+138-jigsaw-nightly-h5561-20161003
java.specification.version: 9
java.vm.specification.version: 9

这并不是过度的信息提示,因为它是运行在一个早期的访问构造上。在将来 java.version 会报告字符串会像 9.1.2 这样, 它遵循的模式是 $MAJOR.$MINOR.$SECURITY :

第 3 段(可获 0.83 积分)
  • $MAJOR 标记了Oracle计划每两三年发布主要的版本号。
  • $MINOR 所标记的是对的Bug修复以及一些其它细节之间例行发布的小版本号——当主版本号发布时,这个标识就会被重置为零。
  • $SECURITY  则非常有意思 --这个标识在每次发布中“内容包括为提升安全性而进行的关键修复”是凸显其重要性,并且在 $MINOR 增长的时候并不会被重置。

我们没有必要解析那些字符串,要获得版本号 ,这里有一个非常不错并为我们所用的小类。

Version version = Runtime.version();
System.out.println("Reported by runtime: " + version);
switch (version.major()) {
    case 9:
        System.out.println("Modularity!");
        break;
    case 10:
        System.out.println("Value Types!");
        break;
}
第 4 段(可获 1.03 积分)

GNU风格的命令行选项

当我们提到java的命令行选项的语法,Java工具有许多特有的风格:

  • 有的是有单破折号表示长版本(-classpath), 其他的 使用双破折号( --gzip
  • 有的使用波折号来分隔单词( --no-gzip ),其他的不会使用(again -classpath)
  • 有的是有单字母形式 (-d),其他的使用双字母形式 (-cp, 这些选项有神严重的错误吗?)
  • 有的使用等号来赋值 (-D<name>=<value>), 其他的需要一个空格 (我从没用过.....)

在Linux和其他基于GNU的操作系统,相比之下,几乎所有的工具使用相同的语法选项:

第 5 段(可获 1.16 积分)
  • 两个破折号表长版本
  • 单词之间用破折号分隔
  • 缩写使用一个破折号和一个字母组成

在不顾一切的勇敢的前行中,Java 9 更改了所有的命令行选项为了匹配这些规则,因此打破了所有的脚本!好吧,这个只是跟你开个玩笑…😎 不过 JEP 293 已经确定了一个准则来反映和适配这些规则,并且新的选项预计也会遵循这个准则。老的选项可能会在某些时候向这种更加清晰的语法迁移,但那并不是 Java 9 的一部分。JEP 包含了许多详细内容以及示例 – 值得一读。

更多来自此作者的详细内容

第 6 段(可获 1.25 积分)

扩展和更新

Java9 附带许多扩展或对现有功能的更新的JEP,下面介绍它们,大体以主题为序,有的概述,也有部分详细说明。

Unicode

然而Java 自身是可以使用UTF-16(是的,你代码可以使用表情包), 属性文件则限制使用 ISO-8859-1,假设你有一个像这样的 config.properties文件

money = € / \u20AC

在Java 8 中来访问这个文件:

money = â▯¬ / €

JEP 226 这样的时代被终结,不再需要 Unicode 转义 。在 Java 9 运行同样的访问代码显示了我们的预期:

第 7 段(可获 1.4 积分)
money = € / €

(在 完整示例 甚至有个表情 😎, 但是我们的代码高亮无法将该表情显示)

值得注意的是,我们有很多种方法用于访问属性文件,但是只有通过 PropertyResourceBundle 访问的这种方法被更新了。 在 the JavaDoc  API 文档中说明了如何精确的检测编码以及如何对其进行配置。默认配置是合理的,尽管它只是让 API 在一般情况下  “可以工作”:

try (InputStream propertyFile = new FileInputStream("config.properties")) {
    PropertyResourceBundle properties = new PropertyResourceBundle(propertyFile);
    properties.getKeys().asIterator().forEachRemaining(key -> {
        String value = properties.getString(key);
        System.out.println(key + " = " + value);
    });
} catch (IOException e) {
    e.printStackTrace();
}
第 8 段(可获 0.85 积分)

你可以在这个示例 中找到使用了 Properties API的代码。如果你想在Java 8上运行用来比较,你会发现java 9 的API 为那些任在使用古老的Enumeration的可怜的开发者们做了一个漂亮的改变

在其它 Unicode 相关的新闻里,Java 9 支持 Unicode 8.0 。耶!(JEP 227, JEP 267)

图像

现在通过图像 I/O 框架(在 javax . imageio 包中)可以支持 TIFF 图像。Java 高级图像(JAI)项目  实现了这种格式的读写。而且现在标准化并封装到 javax.imageio.plugins.tiff. (JEP 262)包中 。

第 9 段(可获 1.2 积分)

Retina-style HiDPI屏幕给桌面UI带来了独特的挑战。Java已经在Mac上解决了该问题,并接下来开始适配Linux和Windows。“窗口和GUI组件基于这个平台推荐规范应该有一个合适的大小,在HiDPI设置里展示的任何默认的缩放比例都应该保证文本清晰,而且图标和图片要保持光滑度,在显示器的像素密度上最好有恰当的细节展现。”(JEP 263)

在Linux平的 Java 桌面的三剑客(AWT、Swing 和 JavaFX)现在可以使用 GTK 3 。目前来说,JVM会默认是GTK2,只有在新系统自带的jdk.gtk.version提示使用或者有GTK3的交互性需求的时候才使用,这个需求在早期就可以检测到。 (JEP 283)

第 10 段(可获 1.6 积分)

JavaFX 使用了过期的 媒体框架 版本,目前已经更新到1.4.4版本。新版本会提高JavaFX重复播放媒体的时候的稳定性和性能(JEP 257)

HarfBuzz 是新的开放类型 布局引擎,并且已经取代了停止更新的ICU。 (JEP 258)

安全性

SHA-3 哈希算法已经实现了SHA3-224, SHA3-256, SHA3-384, 和 SHA3-512 。这些可以通过算法信息 API 来使用 (JEP 287)

当使用SecureRandom(在任何Java 版本上)时,你可以获得一个基于你操作系统本地熵源的原生实现或者获得 一个纯java版本 。后者 “使用的是过时的基于RNG实现的SHA1算法,该算法安全性不如被认可的DRBG[确定性随机位生成器]。”  因为是过时的算法,尤其是嵌入式操作系统依赖于java的变化,它的安全性通过在 NIST 800-90Ar1 描述的DRBG机制的实现来提高。SecureRandom‘s API  已经被改进,其允许传递参数给DRGB和将来的算法(JEP 273):

第 11 段(可获 1.93 积分)
Instantiation instantiation = DrbgParameters.instantiation(128, RESEED_ONLY, null);
SecureRandom random = SecureRandom.getInstance("DRBG", instantiation);

byte[] bytes = new byte[20];
random.nextBytes(bytes);

Published by Ali Bindawood under CC-BY-ND 2.0

CC-BY-ND 2.0下通过Ali Bindawood 发布

新java虚拟机特性

机器可以为我们做所有的事情--我想知道,将来有一天会实现么? 它会获得一些新的特性,并被人们所喜爱,但一旦它在我们生活中出现,我们可能又要花上好几年去适应它。如果不能实现的话,我现在先介绍另一个机器霸主 insect

多版本 JAR包

你可能有时候想为由java版本不同而区别运行的代码--在java 8上做一些事情而在java9上做另外的事情。到目前来说这事有点棘手,但是java 9解决了难题中最重要的部分。现在所有java平台的相关部分都支持创建和认别多版本jar包 ,jar包含相同类型的不同版本,不同的版本对应不同的Java版本。

第 12 段(可获 1.66 积分)

让我们看个示例:

  1. 先创建三个类:Main、VersionDependent用于 Java 8, 还有 VersionDependentfor 用于 Java 9。后续的代码提供一个用于打印的结果,内容是 “Java X version”,其中 X  是 8 或 9。

  2. 然后编译 Main 和 VersionDependent(用于 Java 8 的),结果放在目录 out-mr/java8 上;编译VersionDependent(Java 9 的版本) 放在 out-mr/java9 上。

  3. 现在开始有趣了,来看看怎么打包。下面的命令会创建一个 mr.jar,包含两个 VersionDependent.class (分别来源于 out-mr/java-x 目录)。因为结构明确,所以 java 能选择正确的类:

  4. jar9 --create --file out-mr/mr.jar -C out-mr/java-8 . \
        --release 9 -C out-mr/java-9 .
    
  5. 确实,使用 Java 8 运行 java -cp out-mr/mr.jar ...Main 会输出 “Java 8 version” 而 使用 Java 9 运行会输出 “Java 9 version”。

第 13 段(可获 1.34 积分)

JAR 内部结构看起来像这样:

└ org
    └ codefx ... (moar folders)
        ├ Main.class
        └ VersionDependent.class
└ META-INF
    └ versions
        └ 9
            └ org
                └ codefx ... (moar folders)
                    └ VersionDependent.class

Java 8 或更早的版本会在直接使用 org 下面的类,但新版本会使在 META-INFO/versions 子目录中去找合适的内容来代替默认的。干净利落。

统一的日志

调试 JVM,可能要找到应用崩溃或者性能低下的原因,这已经够复杂了。而不同的日志系统有着完全不同的选项,这并不能让事件变得简单。幸好有即将通过的 JEP 158 和 271,它带来了新的命令行参数 -Xlog(现在不应该是 --log?)可以用来定义极尽详细的日志级别。这里有一些可用的设置:

第 14 段(可获 1.45 积分)
  • 每个消息可以有大量的标签,这依赖于创建和使用它的子系统,如 aregc、模块、oros等。可以选中单独的标签并对其应用其它设置。

  • 当然消息有等级 (error,warning,info,debug,trace,develop) 而且可以按等级进行过滤。

  • 可以为日志文件转换设置输出到 stdout、stderr 或者文件。

  • 然后还有 装饰 – 附加在消息上的有用信息(pid、uptime、…、技术标签和等级都是装饰)。它们都可以被输出。

第 15 段(可获 1.2 积分)

这一些都可以由单独的 -Xlog 选项完成。让我们从简单的记录几个标签开始:

java9 -cp out-mr/mr.jar -Xlog:os,modules,gc ...Main

[0.002s][info][os] SafePoint Polling address: 0x00007feea4c96000
[0.002s][info][os] Memory Serialize Page address: 0x00007feea4c94000
[0.002s][info][os] HotSpot is running with glibc 2.22, NPTL 2.22
[0.009s][info][gc] Using G1
Java 9 version

咦,难道没有模块信息?让我们把那个标签改为 debug(如果没有指定等级,默认是 info):

java9 -cp out-mr/mr.jar -Xlog:os,modules=debug,gc org.codefx.demo.java9.internal.multi_release.Main

[0.002s][info][os] SafePoint Polling address: 0x00007f3054a22000
[0.002s][info][os] Memory Serialize Page address: 0x00007f3054a20000
[0.002s][info][os] HotSpot is running with glibc 2.22, NPTL 2.22
[0.009s][info][gc] Using G1
[0.059s][debug][modules] set_bootloader_unnamed_module(): recording unnamed module for boot loader
[0.063s][debug][modules] define_javabase_module(): Definition of module: java.base, version: 9-ea, location: jrt:/java.base, package #: 159
[... snip ... many, many more module messages ... ]
第 16 段(可获 0.53 积分)

太多了,不过我们可以看到这样:

[0.079s][info][modules,startuptime] Phase2 initialization, 0.0366552 secs

嘿,这是 info!为什么之前它没有显示出来?!Hey, that’sinfo! Why did it not show up before?! 令人惊异的是它有两个标签,在命令中只匹配其中一个标签是不够的——必须匹配所有标签。我们可以通过 modules+startuptime 或者使用通配符来扩展匹配:

java9 -cp out-mr/mr.jar -Xlog:os,modules*,gc* ...Main

[0.002s][info][os] SafePoint Polling address: 0x00007f9c7f307000
[0.002s][info][os] Memory Serialize Page address: 0x00007f9c7f305000
[0.003s][info][os] HotSpot is running with glibc 2.22, NPTL 2.22
[0.007s][info][gc,heap] Heap region size: 1M
[0.009s][info][gc     ] Using G1
[0.009s][info][gc,heap,coops] Heap address: 0x00000006c6200000, size: 3998 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
[0.077s][info][modules,startuptime] Phase2 initialization, 0.0367418 secs
Java 9 version
[0.090s][info][gc,heap,exit       ] Heap
[0.090s][info][gc,heap,exit       ]  garbage-first heap   total 256000K, used 2048K [0x00000006c6200000, 0x00000006c63007d0, 0x00000007c0000000)
[0.090s][info][gc,heap,exit       ]   region size 1024K, 3 young (3072K), 0 survivors (0K)
[0.090s][info][gc,heap,exit       ]  Metaspace       used 4225K, capacity 4532K, committed 4864K, reserved 1056768K
[0.090s][info][gc,heap,exit       ]   class space    used 414K, capacity 428K, committed 512K, reserved 1048576K
第 17 段(可获 0.75 积分)

瞧,即使是垃圾收集器也有信息显示出来——在这个示例中,是关于退出和堆的。

这些都只是表面上的——还有更多选项可用于调整。JEP 很好的解释了它,还包含示例。

事实上,这个改进并不能解决一切问题。因为 JEP 关注于通过它提供基础设施和改变一些(很多?那一定是所有 GC 消息)已经存在的调用,而不是所有事情。虽然我找不到其它使用新机制的日志,但它可能就在那里。

第 18 段(可获 1.35 积分)

命令行参数验证

分享一件有趣的事情:Java 8 虚拟机要在使用到命令行参数的值时才会验证它们。不幸的是,这在应用程序的生命周期中可能会显得有些迟了,我可以预见随着潜在的错误而来的崩溃。来看一个例子:

java -cp out-mr/mr.jar -XX:BlockLayoutMinDiamondPercentage=120 ...Main

我不知道 BlockLayoutMinDiamondPercentage 有什么作用(也懒得查),看起来 120不是一个有效的百分比。但 Java 8 没有收到干扰,愉快地执行了 JAR 包中的代码——显然这并不是程序运行所需要的值。也许 120 就是一个有效的值呢?Java 9 并不这么看:

第 19 段(可获 1.53 积分)
java9 -cp out-mr/mr.jar -XX:BlockLayoutMinDiamondPercentage=120 ...Main

intx BlockLayoutMinDiamondPercentage=120 is outside the allowed range [ 0 ... 100 ]
Improperly specified VM option 'BlockLayoutMinDiamondPercentage=120'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

不错 ... JEP 245 为 Java 9 带来了在开始运行时验证所有输入参数的能力。从上面的例子可以看到,它也输出了解释性的错误消息。

保存栈区域

假如一个线程运行在栈空间外,它会抛出StackOverflowError错误。在用户代码中,这通常表现应用程序的瘫痪,所以没办法进行干预。但是在库里面,尤其是java核心部分,就算会失败也要尽量保持某些不变量。

第 20 段(可获 1.04 积分)

比如 ReentrantLock,一个StackOverflowError错误会导致锁状态的错误,会中断锁的状态,使它不能再锁。假如应用程序决定捕获异常,那这个锁会使所有依赖的线程发生死锁。这会很糟糕。

为关键区域设置保留页面

JEP 270添加新的注解@ReservedStackAccess。有了它,操作模式会被标记为关键区域,即系统如果处于正常状态,则需要完成运行。JVM的行为由于执行栈而被替代。它保存一些额外栈空间,并且无论何时,一个栈溢出错误发生时,它会在栈的注解方法检查是否有任何@ReservedStackAccess。假如有,会预留一定空闲空间用于执行一些额外工作。它一旦完成,异常就不会再抛出。

第 21 段(可获 1.69 积分)

这意味着使用注解不会产生大惊喜,异常还是会抛出,并且线程可能会由于失去了它以同样方式瘫痪。只是当区域代码需要完成,并且要确保在预留空间内完成时,这一操作才有意义。

在栈里保存多大空间?直到现在,默认是一个单一存储页,“实验表明,这足以覆盖java.util.concurrent的临界区已注明的关键部分”。

示例

编写例子会有点难,因为我们很难在托管程序产生堆栈溢出的同时,还能有一部分空间来做些其他的事。在演示项目中,我提供了我认为最佳的解决方案,文中我没有做详细阐述,但我展示了一些以无法控制的形式不断溢出堆栈的代码,其对保留空间无任何作用。

第 22 段(可获 2.21 积分)
int depth;

@ReservedStackAccess
private void determineDepthWithReservedStack() {
    determineDepth();
}

private void determineDepth() {
    depth = 0;
    try {
        recurseToDetermineMaxDepth();
    } catch (StackOverflowError err) { }
    System.out.printf("Depth: %d%n", depth);
}

private void recurseToDetermineMaxDepth() {
    depth++;
    recurseToDetermineMaxDepth();
}

从下往上看,你会发现这是一个无限递归和递增 depth 字段的方法,只是用另外一种方式进行了包装,它能捕获异常并打印 depth 的值。最后,第三个方法请求访问保留堆栈区域。我并没有使用保留的堆栈区域,而是通过递归调用来将它耗尽。这意味着不管调用 determineDepth 还是 determineDepthWithReservedStack,最终都会导致堆栈的溢出,并打印出调用的次数。但输出结果会有所不同,如下:

第 23 段(可获 1.08 积分)
DEPTH USING REGULAR STACK
Depth: 21392

DEPTH USING RESERVED STACK
Java HotSpot(TM) 64-Bit Server VM warning: Potentially dangerous stack overflow in ReservedStackAccess annotated method org.codefx.demo.java9.internal.stack.ReservingStackAreas.determineDepthWithReservedStack()V [1]
Depth: 59544

其输出结果非常不稳定。我猜想这里还有其他机制在工作,而这里我只做了简单演示。

内部构造!

如果你现在已经垂涎三尺,请注意这只是内部的 API。想要通过编译器访问注解,你必须进入基础模块--add-exports java.base/jdk.internal.vm.annotation=<module>,其中<module>或者 ALL-UNNAMED是模块的名称(如果你已经进入)。如果JVM仍然无法读取注解,这时你就需要关闭限制特权的代码 -XX:-RestrictReservedStack。

第 24 段(可获 1.11 积分)

如何在一起编译和运行请看  示例项目 的compile.sh和run.sh

但是,请稍等, 这里还有更多!

是的,这里还有更多,并且这次你不需要等两个月--我保证。第二部分将在下周某个时间段完成,并且更专注于性能提升和编译器的改进。我可以肯定的是时事快报会先睹为快,所以如果你好奇 订阅 (在UTC时间的星期五晚上)或在媒体上关注我们, 或者SitePoint(或者两者同同时关注),这些方式下周都可以为你展示。

如果你喜欢这篇文章,请帮助我们推广这些文字,并告诉你的朋友或同事相关的消息。

第 25 段(可获 1.41 积分)

文章评论