如何从 Java 8 升级到 Java 12,升级收益及问题处理技巧

阅读数:8799 2019 年 4 月 30 日 08:00

如何从Java 8升级到Java 12,升级收益及问题处理技巧

核心要点

  • 从 Java 8 之后,Java 引入了很多有用的新语言特性,以及新工具和性能改善(尤其是垃圾收集相关的优化)。
  • 在选择升级时,我们所面临的选择是升级到最新的 Java(12)并准备每六个月升级一次,还是升级到最新的 LTS(11)版本,这样能够给自己三年的时间再去考虑下一次升级;
  • 不要忽略编译器警告。在现代 Java 领域中,废弃的功能要被更严肃地对待,Java 10 和 Java 11 都删除了 API;
  • Java 9 所带来的一个变化就是内部 API(大多数是 sun.misc.* 打头的包中的类)被隐藏了,导致无法使用了。在 Java 11 中,非核心 JDK 的 API 也被删除了。这些变化可能会影响到你的应用,但是有明确的迁移路径来避免这些问题;
  • 在度过了第一次升级的艰难时期之后,我们至少可以每六个月在最新版本的 Java 上测试一下我们的应用,比如在 CI 环境中。

一般来讲,企业级应用不愿意升级到最新版本的 Java,除非该版本已经得到了充分的证明。Java 9 在 2017 年 9 月发布之后,我们每六个月就会有一个新版本的 Java 发布,这一切变得更具挑战性。所以,尽管 Java 9 发布还不到两年的时间,最新的版本已经是 Java 12 了。

这种变化节奏可能会令人望而生畏,而且从目前主流应用程序所运行的 Java 8 升级到 Java 12 可能会有很多工作要做。在本文中,我们将会看一下:

  • 升级所带来的收益;
  • 升级过程中可能会出现的问题;
  • 关于升级的一些技巧。

为何要升级?

在深入研究如何升级的细节之前,我们应该认真考虑一下为何要升级。

特性

作为开发人员,我们最关心的是语言的每次升级所带来的新特性和 API。从 Java 8 之后,我们已经有了很多的新特性,并且还有改变我们工作方式的新工具。在这里我挑选了一些开发人员认为最新版本的 Java 让他们的工作更容易的新特性。

局部变量类型推断(Local-variable type inference) (即 var)是语法糖的一个绝佳示例,它有助于减少样板式代码。它并不是针对所有可读性问题的解决方案,但是在正确的地方使用它能够帮助代码的读者关注业务逻辑(它要做什么)而不是样板式的代码(它是如何做的)。

针对集合的便利的工厂方法(Convenience Factory Methods for Collections) 能够大幅度简化集合的创建,比如 List、Map 和 Set。这些工厂方法还能创建不可修改的集合,让它们使用起来更加安全。

收集至不可变的集合,这个特性能够针对 Streams 操作使用新的 Collector,它会将结果放到一个不可变的集合中。

Predicate::not 提供了一个很简便的方式来否定断言 lambdas 表达式或方法引用,同样能够减少我们的样板式代码。

Optional 上的新方法能够让我们在使用 Optional 来替换繁琐的 if 语句时,有了更多的函数式编程可选方案。

JShell 是一个 REPL ,它能够让我们运行 Java 编写的代码行,甚至脚本。它是一种体验新特性的好办法,我们可以在本地开发环境中使用它,避免在生产环境的应用代码中采用新版本的 Java。

HttpClient 构建到了 JDK 中。几乎所有人都在以某种形式使用 HTTP,要么通过 Web 应用,要么通过 REST 服务或其他方式。内置的客户端移除了对外部的依赖,同时支持 HTTP 1.1 和 2 的同步和异步编程模型。

JAR 文件的多发布版本(Multi release jar files),这是一个工具库,开发人员可以使用它来支持需要最新版本的 Java,同时还要支持使用旧版本的场景。

JLink 是一个非常棒的工具,由 Java 模块系统(Java Module System)提供,它能够让我们只打包和部署 JDK 中我们真正需要的部分。

性能

通常来讲,新版本都要比之前的版本表现更好。“更好”会有多种表现形式,但是在最近的释放版本中,我们看到了启动时间的改善、内存消耗的减少,以及使用特定的 CPU 指令从而让代码使用更少的 CPU 周期。Java 9、10、11 和 12 都对垃圾收集功能进行了重大的变更和改进,包括将默认的垃圾收集器改为G1 、对G1 的改进以及三个实验性的垃圾收集器(Java 11 中的 Epsilon ZGC ,Java 12 中的 Shenandoah )。三个收集器看起来可能有些过分了,但是每个收集器都针对不同的使用场景进行了优化,所以现在你可以从中选择一个最适合你的应用程序的现代垃圾收集器。

成本的削减

Java 最近版本的改善可能会为你带来成本的削减。尤其是像 JLink 这样的工具能够减少部署制件的大小,再加上内存使用方面的改善,这都会降低云计算的成本。

另外还有一项潜在的收益,那就是使用现代版本的语言会吸引更多的开发人员,因为很多开发人员都希望有机会学习新的东西并更新自己的技能。使用现代版本的 Java 有利于开发人员的留任和招聘,最终会影响团队的运行成本。

升级的注意事项

从 Java 8 以来,很多事情都发生了变化,而且不仅局限于语言级别的特性。甲骨文公司开始发布两个不同的构建版本,它们有着不同的许可证(其中一个是商业构建版本,在生产环境使用的话需要付费,另外一个是开源的 OpenJDK 构建版本),并且更改了他们的升级和支持模型。这在社区引发了很多的争论,但是最终在选择使用哪个 JDK 方面,我们会有更多的选择。我们必须要考虑采用什么方案以及要从 JDK 中得到什么。

该选择哪个版本?

鉴于最新的版本已经是 Java 12 了,在从 Java 8 进行升级时,我们似乎有很大的可选余地。实际上,这个选择要简单得多。现在,我们每六个月就会有一个发布版本,每个新的发布版本都会取代之前的版本。唯一例外的是每三年会有一个长期支持(Long Term Support,LTS)的发布版本。这样的话,就允许组织选择最合适的升级路径,要么每六个月就升级到最新的版本,要么采用传统方式每三年进行一次大的升级,也就是升级到 LTS 版本。Java 8 是 LTS 版本,但是甲骨文在今年一月已经停止了在商业应用中对它的(免费)更新。Java 11 是目前的 LTS 版本,尽管甲骨文目前还没有为其提供免费的商业更新就发布了 Java 12,但是有很多组织都提供了大量的 JDK 构建版本,这些组织将会提供至少三年的更新。

你所面临的选择就是,要么升级到最新版本的 Java(12)并且为每六个月一次的更新做好准备,要么升级到最新的 LTS 版本(11),这样的话,你会有三年的时间考虑下一次的升级。我们建议更大的组织采用的策略是从 LTS 版本升级到下一个 LTS 版本,而创业型的小型组织可以每六个月升级一次。更快速且可预测的发布节奏所带来的好处是它能够同时支持这两种方案。

使用哪个构建版本?

我们不能仅仅认为甲骨文没有为 LTS 版本提供免费更新,就认为无法在生产环境使用 LTS 版本并获取免费更新。现在,有很多的组织和厂商都在提供 OpenJDK (JDK 的参考实现,甲骨文的商用 JDK 甚至都是基于它构建的)的构建版本。我在这里不会对它们一一列举,请参阅这个列表,它涵盖了免费 OpenJDK 构建版本的提供商、免费公开更新可以维护到什么时间以及商业支持的详细信息。

看上去,这比以往要复杂得多。当然,还有很多其他的因素要考虑,这是更多选择所带来的副作用。Java Champions 正在就许可证、支持、更新以及不同方案的话题准备一个更加完整的讨论,在 QCon London 2019 上也有关于该话题的问答环节

Java 9 会破坏一切吗?

很多开发人员在从 Java 8 进行升级时,最主要的顾虑在于 Java 9 所带来的重大变化,他们害怕这些变更会破坏掉应用程序。其中一项变更就是对内部 API 的封装,这意味着 JDK 中一些过去可用的方法无法使用了。在 Java 9 发布的时候,这一点,再加上对 tools.jar 和 rt.jar 的移除导致很多人感到非常担心,但事后证明,相对于应用开发人员,它们对库、框架和语言开发人员所带来的问题更严重。

如果你的应用没有做什么不应该做的事情(比如使用内部 API 或者已废弃的方法),迁移至 Java 9 或更高版本并不像看上去那么恐怖。社区面临的很多问题实际上已经通过我们所使用的构建工具和库解决掉了。

升级指南

每个升级过程都是与要迁移的应用相关的。但是,有一些基本的良好实践能够帮助我们简化过程。我们按照要处理的顺序将它们列到了这里,你会发现最初的一些步骤根本就不需要升级版本的 JDK。

处理编译告警

警告的出现都是有原因的,如果你遇到警告的话,它们通常预示着一些将来会消失掉的特性。如果你在 Java 8 JDK 上使用 Java 8 的话,那么可能会看到废弃提醒的警告,或者某些不该使用的特性的警告(参见图 1),在尝试升级到更新版本的 JDK 之前,请解决这些警告。

如何从Java 8升级到Java 12,升级收益及问题处理技巧

图 1:来自 JDK 8 的编译器告警示例

注意,在现代 Java 中,废弃的功能要被更严肃地对待, Java 10 Java 11 都删除了 API。

检查对内部 API 的使用

Java 9 的一个变化就是内部 API(大多数都是以sun.misc.*开头的类)隐藏了起来,无法继续使用了。

JDK 中有一个名为 jdeps 的工具,你可以使用-jdkinternals标记来运行它,这样的话能够检查一组类是否用到了它不该使用的内容。举例来说,如果我在项目的输出中运行如下的命令:

复制代码
> $JAVA_HOME\bin\jdeps -jdkinternals .

我会得到如下的输出:

复制代码
. -> /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar
com.mechanitis.demo.sense.twitter.connector.TwitterOAuth (.)
-> sun.misc.BASE64Encoder JDK internal API (rt.jar)
-> sun.misc.Unsafe JDK internal API (rt.jar)
Warning: JDK internal APIs are unsupported and private to JDK implementation that are subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependency on any JDK internal APIs. For the most recent update on JDK internal API replacements, please check: https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
JDK Internal API Suggested Replacement
---------------- ---------------------
sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8
sun.misc.Unsafe See http://openjdk.java.net/jeps/260

这个工具不仅能够识别出使用了内部 API 的类,还提供了该使用什么替代方案的建议。

升级构建工具

如果你使用 Maven 或 Gradle 的话,也需要进行升级。

Gradle

Gradle 5.0 引入了对 Java 11 的支持,所以我们至少要使用 5.0 版本。当前的最新版本是 5.4.1 ,它提供了对 Java 12 的支持。

Maven

我们至少要使用 3.5.0(最新的版本是 3.6.1 ),需要确保 Maven 编译器插件至少是 3.8 版本:

复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>11</release> <!-- or 12 -->
</configuration>
</plugin>

更新依赖

关于迁移至 Java 9 或更高版本你所听说的一些问题,都是库和框架需要面对的(而且可能已经被它们所修复了)。例如,很多框架在幕后都会使用反射和内部 API。为了保证你的应用能够继续正常运行,你需要确保所有的依赖项都是最新的。很多库都进行了更新,以便于支持 Java 9 和更高版本,社区正在努力确保这个过程能够持续下去。

有些依赖项可能需要替换。举例来说,很多库都已经使用 Byte Buddy 来实现代码生成和代理,因为它可以与所有现代版本的 Java 兼容。在研究迁移至 Java 11 的时候,你必须要深入理解你的依赖关系,并搞清楚它们是否已经进行了更新以支持 Java 8 以后的版本。

添加对已移除功能的依赖

非 JDK 核心的 API 已经被移除掉了,这包括 Java EE 和 Corba 模块以及 JavaFX 。这个问题解决起来通常很简单,只需要在你的依赖管理中添加正确的库就可以了。例如,在 Gradle 或 Maven 中添加对 JAXB 的依赖。JavaFX 稍微复杂一些,但是在 OpenJFX 站点上有非常棒的文档

使用新 JDK 运行应用

要使用最新版本的 Java,你并不需要重新编译,这就是语言开发人员如此努力保持向后兼容性的原因之一。你可以无需任何代码变更就可以在持续集成环境(举例来说)中使用新的 JDK 来运行你的应用。

使用新的 JDK 进行编译

在前面的步骤中,我们依然可以使用 Java 8 编译应用。只有在完成了这些步骤之后,你才应该考虑针对 Java 11 或 12 进行编译。请记住,如果不想使用新特性的话,我们可以使用较低的语言版本编译应用,这样的话,我们就能够继续回滚到旧版本上。举例来说,在 Maven 中:

复制代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>8</release>
</configuration>
</plugin>

在 Gradle 中:

复制代码
sourceCompatibility = JavaVersion.VERSION_1_8

开始使用新特性

如果所有的事情都运行正常,所有测试都能运行通过而且所有的功能都有很好的性能表现,甚至在生产环境安全运行一段时间之后,我们才能考虑使用新的语言特性。

另外,还需要记住,尽管 Java 9 版本完全是关于 Java 模块系统的,但是应用程序并不一定必须要使用它,即便我们已经迁移到了支持该功能的版本上的时候依然如此。不过,如果你对采用模块系统感兴趣的话,我们在 InfoQ 上有一个使用指南

总结

Java 8 之后,发生了很多的变化:每六个月就会有一个发布版本;许可证、更新和支持方面都发生了变化;JDK 的来源可能也改变了。除此之外,当然还有新的语言特性,包括 Java 9 所带来的重大变化。但是,现在 Java 11 取代 Java 8,成为了最新的 LTS 版本,主要的库、框架和构建工具都采用了最新版本的 Java,所以这是将应用升级至 Java 11 或 12 好时机。

在度过了第一次升级的艰难时期之后,我们至少可以每六个月在最新版本的 Java 上测试一下我们的应用,比如在 CI 环境中。理想情况下,你甚至可以跟随六个月的发布节奏,这样每六个月都能使用最新版本的 Java 并且能够在第一时间使用新的特性。

关于作者

Trisha Gee是 Java Champion,她曾经为多个行业中各个规模的公司开发 Java 应用,包括金融、制造业、软件行业和非盈利组织。她在 Java 高性能系统方面有着丰富的经验,热衷于提升开发人员的生产力,并参与了开源项目的开发。她是 JetBrains 的 Java 开发人员倡导者,这使得她能够掌握最前沿的 Java 技术。

查看英文原文 Upgrading from Java 8 to Java 12

评论

发布
用户头像
让我们用热烈的掌声欢迎下一个版本帝:Java
2019 年 04 月 30 日 10:27
回复
用户头像
相对于一个稳定的系统,升级带来的未知风险是得不偿失的,一个应用程序或者系统的好坏,其根本原因不是在jdk性能有多大的提升或者用了多少语法糖。。。。
2019 年 04 月 30 日 09:45
回复
没有更多了