在万物云原生下的环境下,Java的市场份额也因耗资源、启动慢等缺点,导致在云原生环境里被放大而降低,通过这篇文章,读者可以更好地了解如何在云原生环境下通过升级相关版本和使用GraalVM打出原生镜像到方式,优化Java应用的性能和资源利用率,使Java应用更好地适应云原生环境。
现在我们的项目能正常运行,为什么要耗费大量人力重构?
在云原生时代,随着技术的日新月异变化,我们寻找最佳技术解决方案的道路上总是充满坎坷。作为分布式应用的主流技术,Kubernetes(简称k8s)和Java在这个时代交织出一段爱恨情仇般的佳话。那云原生下这两个技术究竟是敌是友,需要我们进行深入分析。首先,我们站在云原生的角度来看,难以忽视的是k8s 的确被时代认可,尤其在微服务架构、容器化部署和资源管理等方面。其强大的自动化、弹性伸缩和容错能力成为了很多软件系统的最佳实践。而Java作为一门拥有28多年历史的编程语言,无论是在市场份额和生态圈都有无可撼动的地位。Java在分布式系统、中间件、业务逻辑处理等方面有着丰富的实践经验,为开发人员提供了良好的编程体验和广阔的生态支持。而在众多应用架构的背后的技术架构都离不开Kubernetes和Java。从这个角度看来,它们似乎是一对黄金搭档,但关系到具体实践过程时,细节往往能决定成败。我们来看看这两个技术在实际操作中产生的爱恨情仇。
在云原生架构中,k8s为应用提供了资源限制与管理的能力,使得应用能够按需分配资源。然而,Java在处理资源方面存在一定的局限性。因为JVM在内存管理方面依赖于Xms和Xmx等参数进行配置,导致应用在Kubernetes中的资源管理相对复杂。在这期间我们发现java也不甘示弱 在JVM层面对资源管理等方面也进行了改进 比如增加了Containters标志,使得Java应用在k8s环境中的资源配置更为简化。
在微服务架构中,服务发现和负载均衡是关键。过去,Java生态中有很多解决方案,例如Eureka、Zookeeper等,这让java在服务发现和负载均衡方面有多种选择,
但在k8s环境中,天然具备服务发现和负载均衡的能力,如果需要转到云原生就需要对微服务进行调整以适配k8s环境。
k8s在持续集成与持续部署方面具备很强的能力,与GitLab、Jenkins等CI/CD工具的结合可以为Java应用提供极致的开发、构建和部署体验。通过将k8s和Java生态中优秀的CI/CD工具整合在一起,架构师能够实现敏捷的开发、快速的迭代和高效的运维。
在k8s的世界里,Java应用程序的部署和管理已然变得越来越高效。但随着微服务、Serverless 架构的兴起,Java的致命缺陷开始暴露出来。首先是启动时间,Java应用程序在启动时需要加载众多类文件,带来了相对较长的启动时间。再者,Java应用程序的内存占用往往较高,这在云原生环境中意味着更高的运行成本。然后我们开始寻找替代方案,以求摆脱Java在云原生领域的束缚,进而拥抱更快、更轻量级的应用程序运行环境。
于是,GraalVM应运而生。GraalVM是一个高性能的Runtime,支持多种编程语言(这也是它通用的原因)。它提供了Java与云原生领域的桥梁,可以将Java代码编译成本地可执行文件,这意味着更快的启动速度和更低的内存使用。与之同时,GraalVM还提供了对Java的完整支持,便于将现有的Java应用程序迁移到云原生领域。此外,GraalVM还集成了许多强大的特性,如 (AOT)编译、即时编译(JIT)以及强大的GC回收机制。这些特性使得GraalVM成为了在云原生领域的新宠。
如果已经拥有一个功能完备且经过充分测试的Java应用程序,那么将其转换为native-image可以最大程度地复用现有代码库,减少重新编写应用程序所需的工作量和风险。此外我们部门项目都是Java,使用native-image能有效降低迁移成本。
如果队伍中已经具备较为丰富的Java开发经验,那么将Java应用转换为native-image会降低学习成本和培训成本,相较于使用全新的编程语言来重写应用,这会使得开发者更容易上手。
虽然Java应用在某些性能方面可能不如其他编程语言,但通过使用native-image,可以将Java应用转换为与C或C++等编译型语言类似的二进制文件,从而缩小性能差距。native-image为应用带来更快的启动速度和更低的内存占用,而这在k8s这种容器化的环境中尤为重要。
Java具有良好的跨平台兼容性,而借助GraalVM进行native-image编译,我们依然可以保留这一特性。这为应用部署提供较高的灵活性,降低了环境迁移和维护的复杂度。
Java拥有庞大的开发社区和一个成熟、丰富的生态系统,提供了大量的库和框架来支持各种应用的开发。使用native-image可以更好地利用这些资源,简化应用的开发和维护过程。
采用jdk17+springboot3+native-iamge=原生轻量化服务
对于低频流量的业务系统,采用试点项目的方式保证可行性的落地,根据业务功能进行项目合并,而对于合并后的全新项目,直接升级(jdk 和spring框架) version 完成项目合并后保证java版本的项目跑起来,之后采用native-image的方式打包成原生镜像,让项目在 新特性/稳定性的版本基础上 晋升为真正的轻量化应用。
JDK 8 到 JDK 17 之间引入了许多新的语言特性和功能,如Lambda 表达式、Stream API、Optional 类、默认方法、接口私有方法等。这些特性可以使代码更简洁、可读性更好,并支持函数式编程风格,提高开发效率和代码质量。
JDK 17 中进行了多项性能优化和增强,包括垃圾回收器的改进、JIT 编译器的增强、并发类库的改进等。这些优化可以提高应用程序的性能和吞吐量,特别是在大规模和高并发的情况下。
JDK 9 引入了模块化系统(Java Platform Module System,JPMS),它提供了更好的代码组织和隔离,使得复杂的应用程序可以更容易地维护和扩展。模块化系统还可以帮助识别和解决潜在的依赖冲突问题,提高应用程序的稳定性和可靠性。
JDK 17 在安全性方面有多项改进,包括加强的加密算法(SHA-3、X25519、Ed25519 )、更新的安全协议(TLS 1.3)、安全性增强的 API 等。这些改进可以提升应用程序的安全性,防范潜在的安全漏洞和攻击。
JDK 17 提供了许多工具和性能分析的改进,例如 JShell(交互式编程工具)、Java Flight Recorder(JFR,低开销的性能分析工具)、JDK Mission Control(JMC,性能监控和故障诊断工具)等。这些工具可以帮助架构师和开发团队更好地分析和优化应用程序的性能和行为。
从 JDK 8 到 JDK 17,Java 平台一直在持续改进和演进。JDK 17 是一个长期支持(LTS)版本,意味着它将获得长期的支持和维护,包括安全更新、错误修复和性能改进。作为架构师,选择升级到 JDK 17 可以获得更稳定和可靠的平台,并确保应用程序能够得到长期的支持。
JDK 17 引入了许多新的功能、改进和性能优化。这些包括新的语言特性、增强的性能和安全性、更好的垃圾回收器、模块化系统等。通过将 Spring Boot 升级到版本3,可以利用 JDK 17 的新功能,提高应用程序的性能、稳定性和安全性。
Spring Boot 1.x 系列最初是为 JDK 6 和 JDK 7 开发的,而 Spring Boot 2.x 系列则对 JDK 8 有更好的支持。我们将 JDK8 升级到 JDK 17,将 Spring Boot 从版本1.x、2.x升级到版本3,可以确保应用程序能够充分利用 JDK 17 的新特性,并获得更好的兼容性和性能。
随着时间的推移,Spring Boot 的新版本会修复旧版本中存在的问题、漏洞和错误。通过升级到最新的 Spring Boot 版本,您可以获得这些修复,并提高应用程序的稳定性和安全性。
随着时间的推移,Spring Boot 社区会随着新版本的发布而更新和改进文档、示例和社区支持。通过升级到最新版本,您可以获得最新的文档和社区支持,使您能够更好地使用和维护 Spring Boot 应用程序。
JDK 在不同的版本中引入了新的 API,并对旧 API 进行了修改和弃用。在升级到 JDK 17 之前,需要仔细检查你的代码,查找并解决所有使用了已弃用的 API 的地方。还应该熟悉 JDK 9、JDK 11、JDK 14 和 JDK 17 之间的主要 API 变动,以便在升级过程中进行必要的修改 (servlet api没报错先忽略)。
1.JDK 9 引入了模块化系统,将 JDK 中的功能划分为一组模块。如果你的应用程序依赖于 JDK 8 中不存在的模块,或者使用了模块化系统中的新特性(如模块路径),在升级到 JDK 17 时需要进行相应的调整。确保你的应用程序的模块声明和依赖关系与 JDK 17 兼容,并进行必要的迁移工作(jdk8升的忽略)。
JDK 8 到 JDK 17 期间,JDK 团队可能会移除一些不再支持的功能。在升级之前,查看 JDK 的发布说明和文档,了解哪些功能将被移除或弃用。检查你的代码是否使用了这些功能,以便及时进行修改和适应。
JDK 17 中引入了一些新的字节码指令和运行时优化,这可能会导致你的应用程序在升级后出现不同的行为。特别是在使用反射、字节码增强和动态代理等技术的情况下,需要仔细测试和验证升级后的应用程序是否仍然正常运行。
升级 JDK 可能会对你的应用程序依赖的第三方库和工具产生影响。在进行升级之前,确保你所使用的所有库和工具与 JDK 17 兼容,并及时更新到最新版本。此外,还要留意一些特定的库或工具可能会有与 JDK 版本相关的限制或要求。
升级 JDK 可能会带来性能改进和 bug 修复,但也可能引入新的性能问题或稳定性问题。在升级之后,通过全面的性能和稳定性测试,确保你的应用程序在新的 JDK 版。
如果代码使用了 JDK 8 中被移除或弃用的功能。查找 JDK 17 的发布说明,找到替代的功能或方法,并进行相应的修改。比如sun.misc.Unsafe 其大部分方法已被标记为 @Deprecated
在升级到 JDK 17 后,你的应用程序出现了意料之外的行为。 使用调试工具和日志记录,对代码进行详细调查,查找与字节码和运行时差异相关的问题,并进行相应的修复。例如,JDK 17 引入了 instanceof
指令的新变体 instanceof
,用于改进 instanceof
运算符的性能。如果你的代码在 JDK 8 中使用了自定义的字节码操作,需要检查是否需要相应地调整代码。
升级到 Spring Boot 3.x 可能需要更新你的应用程序的依赖项,如 Spring Framework、Hibernate 等。解决方法:查看 Spring Boot 的文档和依赖项管理,了解每个版本所需的依赖项版本,并更新你的项目配置文件 Maven 以使用相应的版本。确保所有依赖项与 Spring Boot 3.x 兼容。
Spring Boot 3.x 可能引入了新的配置属性或更改了现有属性的名称或行为。解决方法:仔细检查你的应用程序的配置文件,特别是 application.properties
或 application.yml
,根据 Spring Boot 的文档和迁移指南,更新和调整属性的名称和用法。
Spring Boot 3.x 可能修改了某些自动配置类或默认行为,可能会影响你的应用程序的行为。 查看 Spring Boot 的升级文档和发布说明,了解哪些自动配置类发生了变化,并在必要时进行相应的修改和调整。
Spring Boot 3.x 可能引入了新的测试框架或更改了现有的测试注解和行为。 查看 Spring Boot 的测试文档,了解测试框架的更新和变化,并相应地修改和调整你的测试代码
在 Spring Boot 2.x 版本中,许多 Spring Boot Starter 的命名方式发生了变化。例如,原来的 spring-boot-starter-web
变为 spring-boot-starter-webflux
,spring-boot-starter-data-jpa
变为 spring-boot-starter-data-jdbc
等。Spring Boot 2.x 中,默认的缓存自动配置使用 JCache(JSR-107)作为缓存提供者在 Spring Boot 3.x 中,缓存自动配置发生了变化。Spring Boot 3.x 引入了对 Caffeine 缓存提供者的默认支持
在社区版和企业版本中,如果不指定GC ,默认都使用的是 Serial GC,而在企业版中可以指定使用 G1 垃圾回收器。G1 垃圾回收器是一种现代化的低延迟垃圾回收器,适用于大部分应用场景。而在在企业版中,引入了名为 GraalVM Native Image 的功能,它使用了不同的垃圾回收器。GraalVM Native Image 是一个 AOT(Ahead of Time)编译器,可以将 Java 程序编译成本地机器码,从而提供更快的启动时间和更低的内存占用。对于 Native Image,GraalVM 企业版使用了垃圾回收器 Substrate VM,它是专门为 AOT 编译的应用程序设计的。
GraalVM 企业版通常会提供更多的性能优化功能和选项。这包括对即时编译(Just-in-Time Compilation)的改进、代码优化和程序分析等方面的增强。企业版的目标是提供更高效、更高性能的运行环境。
企业版可能提供一些额外的工具、库和扩展,以满足企业级应用程序的需求。这些工具可能包括性能分析器、调试器、内存分析工具和性能监控等。
GraalVM 企业版通常会提供一些安全增强功能,以提供更好的应用程序安全性和保护。这可能包括对代码审计、漏洞扫描和安全加固方面的支持。
企业版可能提供更好的跨语言集成支持。GraalVM 企业版具有更广泛的语言支持,包括 Java、JavaScript、Python、Ruby 等,使得在混合语言环境中开发和运行应用程序更加便捷。
企业版提供商业支持和服务,包括错误修复、安全更新和技术支持。这可以确保您在使用 GraalVM 时获得及时的支持和帮助。
新版本的JDK和Spring Boot通常会引入性能改进和优化,包括内存管理、垃圾回收、线程处理等方面的优化。这可能会减少应用程序的内存占用和CPU负载,从而提高系统的响应速度和吞吐量。再集成native-image 编译生成的原生镜像的启动速度往往比JVM的启动速度快得多,这对于有很多短暂任务的应用场景(例如serverless或者函数计算)非常有利
JDK的每个版本都在内存管理和垃圾回收方面进行了改进。新的版本通常会引入更高效的垃圾回收算法和内存管理策略,使应用程序能够更有效地利用内存资源,并减少垃圾回收的停顿时间。这将有助于降低内存占用和减少GC相关的CPU开销,而采用原生镜像后,通常具有比基于JVM的应用更低的内存占用,这有利于降低运行时的资源消耗。
新版本的Spring Boot可能会引入更好的资源管理和优化机制。这包括对数据库连接、线程池、缓存等资源的更有效的管理和利用,以减少不必要的资源消耗,提高系统的资源利用率,而使用 native-image 编译后的本地机器代码 更加显著减小应用程序的内存占用。因为 native-image 只包含应用程序所需的运行时组件和库,而不需要整个 JVM 的额外开销 镜像大小能减少80%,且编译后的本地机器代码不再依赖于 JVM, 减少应用程序对系统资源的消耗,使得应用程序可以在资源受限的环境中更高效地运行。
新版本的JDK和Spring Boot可能会提供更高效的并发处理机制和多线程编程模型。这将使应用程序能够更好地利用多核CPU,并提高并发性能。通过更好的线程池管理、异步编程模型等,可以减少不必要的线程开销,提高系统的并发能力。
通过优化资源消耗,尤其是内存和CPU,可以降低硬件需求和成本。较低的内存占用和CPU负载意味着可以在相同的硬件配置下运行更多的应用实例,从而节省硬件成本。此外,较低的资源消耗还可以减少运维成本和能源消耗。
JDK 17是一个长期支持版本(LTS),它将获得长期的官方支持和维护,包括安全更新、错误修复和性能优化。这意味着我们的应用程序可以在未来几年内持续受到支持,保持安全、稳定和可靠。
通过升级到最新的JDK和Spring Boot版本,可以跟上技术的演进和发展。利用新的语言特性、框架改进和工具,提高开发效率和代码质量。同时,升级也为我们团队提供了学习和发展的机会,使他们能够跟上行业的最新趋势和技术标准,提高他们的技术能力和知识广度。
随着时间的推移,与JDK和Spring Boot相关的生态系统将持续发展和壮大。升级到最新版本可以使您能够获得更广泛、更成熟的生态系统支持。这包括更多的第三方库、工具、插件和社区资源,可以加速开发过程、解决问题和提供更丰富的功能集。
新版本通常会引入更多的开发工具和改进,旨在提高开发效率和开发人员的体验。例如,新的语言特性、API改进、自动化工具和开发工作流程的优化等,都有助于简化开发任务、减少样板代码,并提高团队的整体生产力。
通过升级到最新版本,公司成员可以参与到技术升级的过程中,提出建议、贡献经验和共享知识。这有助于增强对公司的积极性和归属感,激发他们的职业发展动力,同时也为他们提供学习和成长的机会。
升级到最新的技术版本可以使部门甚至公司保持在技术的前沿,并具备更强的竞争力。公司将能够应对新的技术挑战和项目需求,提供更高质量、更高性能的解决方案,从而在市场上保持竞争优势。
升级到较新的版本可以帮助您为未来做好准备。您的应用程序可以充分利用新的技术和功能,以满足不断变化的业务需求和用户期望。同时,较新的版本通常会更好地适应新的硬件和部署环境,为您的应用程序提供更好的性能、可扩展性和可靠性。
总之,升级JDK和Spring Boot版本,以及使用native-image打出来的原生镜像可以帮助Java应用更好地适应云原生环境,提高应用的性能和可扩展性。这些措施可以帮助Java在云原生环境中更好地发展
作者:京东科技 徐拥
来源:京东云开发者社区