标准化原生 Java:拉近 GraalVM 和 OpenJDK 的距离

Java 主导着企业级应用。但在云计算领域,采用 Java 的成本比它的一些竞争对手更高。原生编译降低了在云端采用 Java 的成本:用它创建的应用程序启动速度更快,使用的内存更少。

那么,Java 用户的问题来了:原生 Java 是如何改变开发方式的?我们在什么情况下应该切换到原生 Java?什么情况下又不应该切换?我们应该使用什么框架?本系列文章将回答这些问题。

原生 Java 对于 Java 在不断演进的云世界中保持相关性至关重要

二十多年来,Java 一直是企业应用程序和网络服务的首选开发语言。它拥有一个非常丰富的中间件、库和工具生态系统,以及一个由经验丰富的开发人员组成的庞大社区。因此,它成为开发基于云的应用程序或将现有 Java 应用程序迁移到云端的明智之选。然而,Java 及其运行时的发展与今天的云计算需求之间存在不匹配的地方。因此,Java 需要做出改变才能在云计算中保持相关性!原生 Java 是最有前途的选择。让我来解释一下传统 Java 和云计算之间的不匹配之处。

Java 虚拟机(JVM)使用自适应即时(JIT)编译来最大化长生命周期进程的吞吐量。峰值吞吐量一直有着最高的优先级,内存便宜且可扩展,启动时间不太重要。

现在,像 Kubernetes 或 OpenShift 这样的基础设施,以及亚马逊、微软或谷歌提供的云服务,都可以通过小型廉价的容器(使用很少的 CPU 和内存资源)进行扩展。由于容器启动的频率更高,固定的 JVM 启动成本在总运行时间中所占的百分比变得更加显著。另外,Java 应用程序仍然需要内存来进行 JIT 编译。那么,如何让 Java 应用程序在容器中高效运行呢?

首先,越来越多的 Java 应用程序以微服务的形式运行,工作负载比单体应用程序要少。所以,它们的应用程序数据集更小,使用的内存也更少。

其次,一些框架(如 Quarkus 和 Micronaut)用离线转换的方式来注入所需的服务,而不是在启动时进行非常消耗资源的动态注入和转换。

第三,缩短 JVM 启动时间已经被证明是非常困难的。我们最好避免在每次启动时预先计算相关的已编译代码、元数据和常量对象数据。OpenJDK 已经尝试过多次,最著名的是 jaaotc AOT 编译器和类数据共享。不过 jaotc 已经被放弃了,类数据共享仍在进行当中。OpenJ9 是一个不同于 OpenJDK 的 Java 实现,它在 AOT 编译方面取得了一些显著的进展,但还没有被广泛采用。

做好这些优化是很难的,因为 JDK 运行时也是位于底层硬件和操作系统之上的一个抽象和可移植层。预先计算可能会带入一些构建时假设,而这些假设在运行时不再有效。这个问题可以说是原生 Java 面临的最大挑战。这就是为什么之前的努力都集中在为相同的运行时预生成内容上。不过,Java 的动态特性又带来了两个阻碍性问题。

首先,与其他 AOT 编译语言相比,JVM 和 JDK 维护了一个相对丰富的元数据模型。保留类结构和代码信息有助于运行时在加载新的类文件时对代码进行编译和重新编译。这就是为什么对于一些应用程序来说,元数据占用的资源相对于应用程序数据来说同样重要。

第二个问题是,大多数预先生成的代码和元数据链接必须是间接的,这样就可以在以后发生变更时重写它们。这样做的代价是双重的:将预生成的内容加载到内存中需要链接引用,而使用间接访问和控制传输会降低应用程序的速度。

原生 Java 通过一个直接的简化策略来解决这些问题:不支持动态演化的应用程序。这种策略就是让紧密链接的小型可执行文件在启动时预先计算所有的初始代码、数据和元数据的,以此来实现快速启动和较少的资源占用。这确实是一种解决方案,但也付出了一些代价。况且,它也不能解决匹配构建时假设与运行时配置的问题。

原生 Java 还有待完善

乍一看,打包方式似乎是 GraalVM Native 和 JVM 的主要区别。JVM 应用程序需要目标主机安装 Java 运行时,包括 Java 二进制文件、各种 JVM 库、JDK 运行时类和应用程序 JAR 文件。

相比之下,GraalVM Native 将这些 JAR 文件作为构建时的输入,并加入 JDK 运行时类和一些额外的 Java 代码,以提供与 JVM 等价的功能。它将所有这些东西编译并链接成某个目标 CPU 和操作系统的原生二进制文件。这个二进制文件不需要进行类加载或 JIT 编译。

这个完整的 AOT 编译有两个关键细节:首先,它需要知道所有将要被执行的方法所属的类。其次,它需要对目标 CPU 和操作系统有详细的了解。这两个要求都带来了重大挑战。

封闭式世界假设

第一个要求也被称为封闭式世界假设。封闭式世界应该只包含实际运行的代码。要封闭一个应用程序,首先要确定 main 方法显式引用了哪些类和方法。我们可以对类路径和 JDK 运行时中的所有字节码代码进行相对简单的分析。不幸的是,仅通过名字来跟踪对类、方法和字段的引用是不够的。

链接——Java 提供了类、方法和字段的间接链接,但不显式提到它们的名字。实际链接的内容取决于复杂的应用程序逻辑,这对 AOT 分析来说是不

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值