首页 > IT职业 > IT生活 > 进击的 Java ,云原生时代的蜕变
导读:云原生时代的来临,与Java 开发者到底有什么联系?有人说,云原生压根不是为了 Java 存在的。然而,本文的作者却认为云原生时代,Java 依然可以胜任“巨人”的角色。作者希望通过一系列实验,开拓同学视野,提供有益思考。
在企业软件领域,Java 依然是绝对王者,但它让开发者既爱又恨。一方面因为其丰富的生态和完善的工具支持,可以极大提升了应用开发效率;但在运行时效率方面,Java 也背负着”内存吞噬者“,“CPU 撕裂者“的恶名,持续受到 NodeJS、Python、Golang 等新老语言的挑战。
在技术社区,我们经常看到有人在唱衰 Java 技术,认为其不再符合云原生计算发展的趋势。我们先抛开这些观点,首先思考一下云原生对应用运行时的不同需求。
熟悉Spring框架的开发者大多对 Spring Petclinic 不会陌生。本文将借助这个著名示例应用来演示如何让我们的Java应用变得更小,更快,更轻,更强大!
我们fork了IBM的Michael Thompson的示例,并做了一些调整。
首先,我们会为PetClinic应用构建一个Docker镜像。在Dockerfile中,我们利用OpenJDK作为基础镜像,安装Maven,下载、编译、打包Spring PetClinic应用,最后设置镜像的启动参数完成镜像构建。
构建镜像并执行
检查一下构建出的Docker镜像, ”petclinic-openjdk-openj9“ 的大小为871MB,而基础镜像 ”adoptopenjdk/openjdk8“ 仅有 300MB!这货也太膨胀了!
原因是:为了构建Spring应用,我们在镜像中引入了一系列编译时依赖,如 Git,Maven等,并产生了大量临时的文件。然而这些内容在运行时是不需要的。
在著名的
软件12要素 第五条明确指出了,”Strictly separate build and run stages.“ 严格分离构建和运行阶段,不但可以帮助我们提升应用的可追溯性,保障应用交付的一致性,同时也可以减少应用分发的体积,减少安全风险。
Docker提供了Multi-stage Build(多阶段构建),可以实现镜像瘦身。
我们将镜像构建分成两个阶段:
查看一下新镜像大小,从 871MB 减少到 167MB!
镜像瘦身之后将大大加速应用分发速度,我们是否有办法优化应用的启动速度呢?
为了解决Java启动的性能瓶颈,我们首先需要理解JVM的实现原理。为了实现“一次编写,随处运行”的能力,Java程序会被编译成实现架构无关的字节码。JVM在运行时将字节码转换成本地机器码执行。这个转换过程决定了Java应用的启动和运行速度。为了提升执行效率,JVM引入了JIT compiler(Just in Time Compiler,即时编译器),其中Sun/Oracle公司的HotSpot是最著名JIT编译器实现。它提供了自适应优化器,可以动态分析、发现代码执行过程中的关键路径,并进行编译优化。HotSpot的出现极大提升了Java应用的执行效率,在Java 1.4以后成为了缺省的VM实现。但是HotSpot VM在启动时才对字节码进行编译,一方面导致启动时执行效率不高,一方面编译和优化需要很多的CPU资源,拖慢了启动速度。我们是否可以优化这个过程,提升启动速度呢?
熟悉Java江湖历史的同学应该会知道IBM J9 VM,它是用于IBM企业级软件产品的一款高性能的JVM,帮助IBM奠定了商业应用平台中间件的霸主地位。2017年9月,IBM 将 J9 捐献给 Eclipse 基金会,并更名 Eclipse OpenJ9,开启开源之旅。
OpenJ9 提供了Shared Class Cache (SCC 共享类缓存) 和 Ahead-of-Time (AOT 提前编译) 技术,显著减少了Java应用启动时间。
SCC 是一个内存映射文件,包含了J9 VM对字节码的执行分析信息和已经编译生成的本地代码。开启 AOT 编译后,会将JVM编译结果保存在 SCC 中,在后续 JVM 启动中可以直接重用。与启动时进行的 JIT 编译相比,从 SCC 加载预编译的实现要快得多,而且消耗的资源要更少。启动时间可以得到明显改善。
我们开始构建一个包含AOT优化的Docker应用镜像
其中 Java 参数
-Xshareclasses 开启SCC,
-Xquickstart 开启AOT。
在Dockerfile中,我们运用了一个技巧来预热SCC。在构建过程中启动JVM加载应用,并开启SCC和AOT,在应用启动后停止JVM。这样就在Docker镜像中包含了生成的SCC文件。
然后,我们来构建Docker镜像并启动测试应用,
可以看到,启动时间已经从之前的 8.2s 减少到 4s,提升近50%。
在这个方案中,我们一方面将耗时耗能的编译优化过程转移到构建时完成,一方面采用以空间换时间的方法,将预编译的SCC缓存保存到Docker镜像中。在容器启动时,JVM可以直接使用内存映射文件来加载SCC,优化了启动速度和资源占用。
这个方法另外一个优势是:由于Docker镜像采用分层存储,同一个宿主机上的多个Docker应用实例会共享同一份SCC内存映射,可以大大减少在单机高密度部署时的内存消耗。
下面我们做一下资源消耗的比较,我们首先利用基于HotSpot VM的镜像,同时启动4个Docker应用实例,30s后利用
docker stats查看资源消耗
然后使用基于OpenJ9 VM的镜像,同时启动4个Docker应用实例,并查看资源消耗
与HotSpot VM相比,OpenJ9的场景下应用内存占用从平均 600MB 下降到 120MB。惊喜不惊喜?
通常而言,HotSpot JIT比AOT可以进行更加全面和深入的执行路径优化,从而有更高的运行效率。为了解决这个矛盾,OpenJ9 的AOT SCC只在启动阶段生效,在后续运行中会继续利用JIT进行分支预测、代码内联等深度编译优化。
更多关于 OpenJ9 SCC和AOT的技术介绍,请参考
思考:与C/C++,Golang, Rust等静态编译语言不同,Java采用VM方式运行,提升了应用可移植性的同时牺牲了部分性能。我们是否可以将AOT做到极致?完全移除字节码到本地代码的编译过程?
为了将Java应用编译成本地可执行代码,我们首先要解决JVM和应用框架在运行时的动态性挑战。JVM提供了灵活的类加载机制,Spring的依赖注入(DI,Dependency-injection)可以实现运行时动态类加载和绑定。在Spring框架中,反射,Annotation 运行时处理器等技术也被广泛应用。这些动态性一方面提升了应用架构的灵活性和易用性,另一方面也降低了应用的启动速度,使得AOT原生编译和优化变得非常复杂。
为了解决这些挑战,社区有很多有趣的探索,
Micronaut 是其中一个优秀代表。与Spring框架序不同,Micronaut提供了
编译时的依赖注入和AOP处理能力,并最小化反射和动态代理的使用。Micronaut 应用有着更快的启动速度和更低的内存占用。更加让我们更感兴趣的是Micronaut支持与Graal VM配合,可以将Java应用编译成为本地执行代码全速运行。注:
GraalVM是Oracle推出的一种新型通用虚拟机,支持多种语言,可以将Java应用程序编译为本地原生应用。
下面开始我们的探险,我们利用 Mitz提供的Micronaut版本PetClinic示例工程并做了一点点调整。(使用Graal VM 19.2)
其中Docker镜像的内容如下
其中
native-image 将PetClinic jar文件转化成可执行文件。构建应用
启动测试数据库
启动测试应用
应用启动速度如闪电般提升至 159ms,仅有HotSpot VM的1/50!
Micronaut和Graal VM还在快速发展中,迁移一个Spring应用还有不少工作需要考虑。此外Graal VM的调试、监控等工具链还不够完善。但是这已经让我们看到了曙光,Java应用和Serverless的世界不再遥远。由于篇幅有限,对Graal VM和Micronaut有兴趣的同学可以参考
作为进击的巨人,Java技术在云原生时代也在不停地进化。在JDK 8u191和JDK 10之后,JVM增强了
在Docker容器中对资源的感知。同时社区也在多个不同方向探索Java技术栈的边界。JVM OpenJ9作为传统VM的一员,在对现有Java应用保持高度兼容的同时,对启动速度和内存占用做了细致的优化,比较适于与现有Spring等微服务架构配合使用。而Micronaut/Graal VM则另辟蹊径,通过改变编程模型和编译过程,将应用的动态性尽可能提前到编译时期处理,极大优化了应用启动时间,在Serverless领域前景可期。这些设计思路都值得我们借鉴。
在云原生时代,我们要能够在横向的应用开发生命周期中,将开发、交付、运维过程进行有效的分割和重组,提升研发协同效率;并且要能在整个纵向软件技术栈中,在编程模型、应用运行时和基础设施等多层面进行系统优化,实现radical simplification,提升系统效率。
本文完成于在参加阿里集团20周年的火车旅途上,9/10阿里年会是非常难忘的经历。感谢马老师,感谢阿里,感谢这个时代,感谢所有帮助和支持我们的小伙伴,感谢所有追梦的技术人,我们一起开拓云原生的未来。
本文作者:易立
本文为云栖社区原创内容,未经允许不得转载。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69915408/viewspace-2657284/ ,如需转载,请注明出处,否则将追究法律责任。