Read this post in other languages:
为您的 Spring Boot 项目选择合适的 Java 运行时乍看可能十分简单。 毕竟,所有运行时都基于 OpenJDK 代码并提供相同的 API。
但并非所有运行时都生来平等。 本文将探讨可能影响您为 Spring Boot 应用程序选择特定 Java 发行版的决定的各种指标。
本文由 JetBrains IntelliJ IDEA 的团队主管
Aleksey Stukalov
和客座作者
Catherine Edelveis
(BellSoft 的技术布道师)撰写
。
开发者的视角
功能兼容性
对于大多数开发者来说,使用哪个 Java 发行版并无太大区别。 无论您是在开发 Spring Boot 应用程序还是根本不使用框架,这都无关紧要。 为什么? 关键在于技术兼容性套件 (TCK)。
JDK 本身只是一个表面,下面隐藏着大量不同的规范。 为了确保 JDK 实现能够正确运行您的 Java 应用程序,该实现通过称为 TCK 的一套庞大测试集进行验证。 通过这些测试的实现被称为“经 TCK 验证的发行版”,这会让开发者充分相信他们的应用程序在所有经 TCK 验证的 Java 运行时中以相同方式运行。
虽然经 TCK 验证的发行版保证了代码的相同功能行为,但可能存在其他差异,后面会详细讨论。
基于上述信息,开发者似乎没有偏爱的 JDK 发行版,但事实并非如此。 我们来看看为什么某些选项优于其他选项。
从供应商网站下载 JDK 的日子已经一去不复返了。 现在,只需安装您的首选 Java IDE,就能确保您拥有必要的运行时。 如果是 IntelliJ IDEA,默认 JDK 将是
JetBrains Runtime
,它是 OpenJDK 的一个复刻,提供了几个增强功能,如用于实现无缝代码更新的高级类重定义功能 (DCEVM),以及一个可选的 JCEF(用于嵌入基于 Chromium 的浏览器的框架)。 虽然这些附加功能可能不在每个开发团队的要求列表中,但 JetBrains Runtime 被嵌入到工具中并作为默认选项,这一点使其成为 Java 开发者中最常用的 JDK 之一。
虽然 JetBrains Runtime 是 IntelliJ IDEA 中的默认选项,但您可以一键下载多个 JDK。 Amazon Corretto、Azul Zulu、BellSoft Liberica JDK(标准版或带有 JavaFX)、Eclipse Temurin、GraalVM CE 或 Oracle GraalVM、IBM Semeru 和 SapMachine 都可以在 IDE 中获得,也同样受开发者欢迎。
Spring Boot 特性
Spring Boot 提供了一种便捷的方式来帮助将应用程序转化成容器镜像,无需编写 Dockerfile(构建包)。 不过,并非所有 Java 运行时都可以用于此目的。 Spring Boot 使用的 Paketo 构建包提供了几种 JDK 发行版作为容器镜像的基础。 默认选择为
Liberica JDK
,这也是 Spring 官方推荐的 Java 运行时。 您也可以使用其他 JDK,但请记住,有些只提供 JDK 版本,这意味着最终的容器镜像将比基于 JRE 的镜像占用明显更多的内存。
如果您使用 Paketo 构建包,在开发时使用相同的 JDK 也有意义。 尽管所有经 TCK 验证的发行版在功能上是兼容的,但在调试内存泄漏、性能问题和其他问题时,使用不同的 JDK 仍然会遇到困难。
DevOps 视角
当您接近上线时,拥有一个 Spring Boot 应用程序在决策中变得更加重要。 现代服务最终都走向了云端,其中 Kubernetes (K8s) 已成为事实上的标准。 因此,我们将此设置作为目标。
调整 K8s 的默认设置
使用 Kubernetes 在云端部署应用程序特别轻松,因为它默认为部署和集群编排提供了良好的设置。 然而,调整默认设置可以为公司节省大量成本,特别是对于内存需求较大的复杂 Spring Boot 应用程序。 下面是一些提高 Kubernetes 中 Java 应用程序性能的建议:
负载测试和压力测试旨在评估应用程序在稳定状态和峰值负载下的内存需求,根据这两项测试的结果设置 K8s pod 的 CPU 和 RAM 限制。
选择合适的垃圾回收器。 除了热门的 ParallelGC 和 G1GC,最新的 Java 版本还包括诸如 ZGC 和 ShenandoahGC(后者不随 Oracle Java 一起提供)之类的新垃圾回收实现,专为大堆实现尽可能减小的延迟而设计。
使用 Kubernetes 探针监测您的 pod 的运行状况,并在出现问题时能够及时反应。
在容器中使用 JRE
容器镜像越小,实例占用的内存越少,因此云费用也越低。 此外,如果贵公司使用自己的 Docker 注册表,随着时间的推移,它往往会越来越臃肿,因此在这种情况下,节省内存也很重要。
容器化的 Spring Boot 应用程序可能会变得相当庞大。 为了让它们保持简洁,您可以选择带有 JRE 和轻量级 Linux 发行版(如
Alpine
或
Alpaquita
)的合适基础镜像。
下表汇总了 Docker Hub 上提供的 JDK 21 的基础容器镜像。 我们比较了不同 JDK 供应商提供的使用基于 musl 或 glibc 的操作系统层的压缩 JDK 和 JRE 镜像大小(评估中未包括 Distroless 镜像,因为它们可能不是某些用例的最佳选择)。 数据收集时间为 2024 年 4 月 23 日。
除了 Liberica JDK 和 Red Hat OpenJDK,所有镜像都基于 Alpine Linux(适用于 musl)或 Ubuntu(适用于 glibc)。 Liberica JDK 镜像基于 Alpaquita Linux,它与 Alpine 完全兼容,但有两种版本:基于 musl 和基于 glibc。 Red Hat OpenJDK 镜像基于 RHEL UBI 9。
Docker Hub 上未提供 Oracle Java 构建。
* Amazon Corretto 的 JRE 镜像不在 Docker Hub 上提供,但可以在 AWS 上获得。
升级到最新的 Java
得益于 JVM 的众多改进,较新的 Java 版本的性能显著高于旧版本的性能。 这意味着升级 Java 版本对于跟上云端应用程序性能的现代要求至关重要。
此外,Spring Boot 3.x 要求使用 Java 17 作为基线,因此如果您想利用您最喜欢的框架的新主要版本的强大功能,升级 Java 版本至关重要。
不过,如果现在不能升级,一些供应商(如 Oracle 和 BellSoft)也提供了将 JVM 17 与 JDK 8 结合的解决方案(对于 JDK 11,BellSoft 也有类似的解决方案)。 这样会使您的应用程序认为它仍然运行在旧的 Java 版本上,但实际上它拥有一个较新的引擎。 对您来说,这意味着什么? 无需升级 Java 或框架,也几乎无需进行代码更改,即可立即缩短延迟并提高吞吐量!
最大限度缩短启动时间
复杂的 Spring Boot 应用程序可能需要几十秒才能启动。 即使是小型参考应用 Spring Petclinic 也需要 5 到 7 秒! 此外,Java 应用程序在预热期间消耗的资源比在稳定状态时需要的更多。 这会导致以下问题:
在预热期间和更大的实例上浪费额外的 CPU 周期预算。
由于服务延迟较高,用户满意度下降。
无法充分利用 AWS Lambdas 或类似服务的潜力。
幸运的是,Spring Boot 3 支持两种最有效的解决方案来处理长时间预热的问题 – GraalVM 原生镜像和检查点协调恢复 (CRaC) 项目。 您只需要选择一家为此功能提供支持的 Java 供应商。
检查点协调恢复 (CRaC)
借助 CRaC,您可以暂停一个正在运行的 Java 应用程序,将其保存到文件,然后从暂停的那一刻起从文件恢复,从而高效地将启动时间从几秒缩短到几毫秒。
Spring Boot 从 3.2 版本开始与 CRaC 集成。 目前有两家 OpenJDK 供应商提供支持 CRaC 的 JDK 构建:Azul 和 BellSoft。 Azul 仅开发 JDK,并依赖 Ubuntu 提供 CRaC 支持。 BellSoft 提供了带有他们自己的 Alpaquita Linux 和 Liberica JDK 的支持 CRaC 的容器镜像,因此您可以在 Docker 容器中使用 CRaC,而无需调整 JDK 和 Linux 或向应用程序添加除
org.crac
依赖项之外的额外配置。
CRaC 项目尚未通过 TCK 验证。 如果您正在寻找可付诸生产的功能,请考虑使用原生镜像技术。
原生镜像技术使开发者能够在封闭世界假设下预先将 Java 应用程序转换为静态原生镜像。 原生镜像的启动几乎是瞬时的,并且占用的内存更少,因为它们不包括 JDK。
除了将原生镜像作为 Oracle GraalVM 的一部分交付的 Oracle 之外,还有两家 JDK 供应商将 native-image 编译器作为其 Java 产品的一部分提供:
Red Hat 的 Mandrel 专为 Quarkus 框架量身定制。
BellSoft 的 Liberica 原生镜像套件用作 Spring 构建包中的默认 native-image 编译器,并
获得了 Spring 团队的推荐
。 它基于 GraalVM CE,但包括在 GraalVM Community Edition 中缺少的 ParallelGC。
SecOps 视角
随着网络攻击在数量和攻击性方面不断增长,每年保持高水平的项目安全性越来越具有挑战性。 为了确保您的 Java 应用程序安全,您必须密切关注依赖项和更新。
使用软件物料清单
软件物料清单 (SBOM) 对于软件供应链安全不可或缺。 它通过提供有关组件版本、许可证和已知 CVE 的数据来帮助您监视所有运行时依赖项的状态。
现在,您可以方便地使用 Maven 和 Gradle 插件为您的项目创建 SBOM,以便您的客户了解依赖项的状态。 不过,
您
也应该对开发中使用的解决方案充满信心,包括 Java 运行时。 对于保持基础架构安全而言,供应商提供 SBOM 的 Java 运行时是一个非常好的选择。
为您用于部署的容器镜像生成 SBOM 也很重要,因为如果您不了解基础镜像的内部情况,就无法在它们感染已知 CVE 时及时反应。 而这反过来会导致您的生产环境极易受到攻击。 您可以使用现有工具(如 Docker Scout)生成 SBOM,但从供应商那里获得现成的 SBOM 更加方便。
定期更新 Java
接收包含安全补丁和关键补丁的季度构建的 Java 运行时有助于保持您的应用程序基础免受已知 CVE 的影响。
主要 JDK 供应商提供季度 PSU 版本,其中的三家(Oracle、BellSoft 和 Azul)还会发布面向 LTS 版本的稳定 CPU 构建以及带有补丁和仅关键修正的最新功能版本。 CPU 构建使您能够在不破坏生产环境的情况下引入安全补丁。
季度版本根据 LTS 版本的支持路线图发布,但 LTS 版本的支持生命周期因供应商而异,下表中对此进行了汇总。
对 Eclipse Temurin 的支持由第三方供应商提供。
在 Spring Boot 开发中使用哪个 Java 运行时可能并不重要,只要它经过 TCK 认证即可。 不过,诸如性能、容器镜像范围、有效云部署的附加解决方案等重要指标使某些运行时更适合您的 Spring Boot 项目。
下表总结了我们在本文中探讨的所有内容,概述了可以帮助开发 Spring Boot 的主要功能以及可以提供这些功能的 Java 运行时。
GraalVM 原生镜像编译器
作为 Oracle GraalVM 的一部分
Liberica 原生镜像套件(用于 Spring Boot 的默认 native-image 编译器)
适用于 Quarkus 的 Mandrel
CRaC 支持
LTS 版本的免费更新生命周期
8.5 年
稳定的 CPU 构建
提高 Spring Boot 2.x 项目性能的解决方案
+(适用于 JDK 8)
+(适用于 JDK 8 和 11)
提供商业支持
仅适用于 AWS
并非来自 Adoptium
Aleksey Stukalov
Today I’m leading the IntelliJ IDEA division at JetBrains, overseeing strategic initiatives and operations within the department. Before joining JetBrains, my career journey took me through various roles: starting as an ordinary developer, being a project manager, serving as a developer advocate, CTO and even as a product manager. Through the entire carrier way my passion has always been leaned towards software engineering work, so I found my zen in frameworks and tooling development. This naturally led me to become a founder of the widely-used JPA/React Buddy plugins, which boast hundreds of thousands of users worldwide. Today both tools are proud members of the JetBrains family!
By submitting this form, I agree to the JetBrains
Privacy Policy
Notification icon
提交此表单,即表示我同意 JetBrains s.r.o. ("JetBrains") 使用我的姓名、电子邮件地址和位置数据向我发送简报和商业通讯,并为此目的而处理我的个人数据。我同意 JetBrains 根据
JetBrains 隐私政策
为此目的使用
第三方服务
处理上述数据。我了解我可以在
我的个人资料
中随时撤回此同意。此外,每封电子邮件中也都包含退订链接。
Submit
Java 22 现已正式发布,IntelliJ IDEA 2024.1 全面支持该版本,您可以使用其中的新功能!
从新手开发者到 Java 专家,从寻求性能和安全功能的大型组织到尖端技术爱好者,从 Java 语言的新特性到 JVM 平台的改进,Java 22 可以满足各种群体的各类需求。
这些 Java 功能在一个又一个版本发布后良好配合,创造出更多可能,深度助力开发者创建解决现有痛点、更强劲且更安全的应用程序。
本博文并未覆盖所有 Java 22 功能。 如果您有兴趣,我建议查看此链接,详细了解 Java 22 中的新增内容和变化,包括 bug。
在这篇博文中,我将介绍 IntelliJ I…
简单攻略:如何抛出 Java 异常
Java 中的异常用于指示程序执行期间发生并扰乱正常指令流的事件。 发生异常时,Java 运行时会自动停止当前方法的执行, 将带有错误信息的异常对象传递到可以处理异常的最近的 catch 块。
虽然妥善捕获和处理异常很重要,但了解如何有效抛出异常也同样重要。 在这篇博文中,我们将探讨引发 Java 异常的细节,涵盖不同类型的异常、如何创建自定义异常等。
如何抛出异常
要让 Java 运行时知道代码中发生了异常,首先必须抛出一个异常。 在 Java 中,您可以使用 throw 关键字调用 Java 虚拟机 (JVM) 中的异常机制:
throw new Exception("Something …
简单攻略:在 Java 中创建继承
在 Java 和其他编程语言中,继承是面向对象编程的基本特性之一。 借助继承,您可以创建从类(基类或超类)派生的类,并重用、扩展或修改超类的行为。 这一原则允许您构建类层次结构和重用现有代码。
Java 本身到处都使用继承:许多 JDK 类继承其他类,并且 Java 中的每个类都隐式扩展 java.lang.Object。 本文不会过多关注这一部分,而主要举例说明如何在代码中使用继承。
假设,您想要在应用程序中创建 Employee 和 Customer 类。 借助继承,您可以编写这两个类,使其从父 Person 类继承 name 和 address 属性。 在代码可重用性和模块化方面,这有多…
Java 最佳做法
好的代码都会遵循一定规则,了解这些规则将增大您成功的几率。 我们将在本文中分享一些 Java 最佳做法,为您提供帮助。 我们将介绍必知提示和技巧,涵盖软件开发的总体建议以及 Java 和项目特定的专业知识。 我们开始吧!
首先,牢记以下有关现代编码的一般规则。
干净好过聪明
代码的主要目的是被理解和维护,而不是炫耀技术能力。 干净的代码会让软件更易于调试、维护和扩展,使参与项目的所有人受益。 复杂并不是荣誉奖章,简单和可读才是。
考虑以下示例。
这是交换变量 `a` 和 `b` 值的非常规方式。 虽然聪明,但乍一看可能会令人困惑。
这是更常见的方式。…