Docker容器中的OpenJ9类共享
通过优锐课核心java学习笔记中,我们可以看到,码了很多专业的相关知识, 分享给大家参考学习。
在容器化环境中启用类共享
OpenJ9初设计为可在2000年代早期的移动设备上运行,它是一种用于云的Java虚拟机,它使用的内存大约是JDK8 Hotspot的一半,而吞吐量却几乎与其相当。这种性能提升直截了当;但是,还有更多的调整可以做。在本文中,了解在容器化环境中运行时如何启用OpenJ9的类共享功能。
如果你不熟悉OpenJ9中的类共享功能,那么教程“ Eclipse OpenJ9中的类共享”(IBM Developer,2018年6月)深入探讨了类共享的工作原理以及为什么要使用它。简短的版本是类共享允许OpenJ9 JVM在Java代码上编译和执行优化,并将该信息缓存在一个公共位置,以供其他OpenJ9 JVM使用。类共享提供了显着的好处,包括提高了启动速度并减少了CPU和内存使用量。
在容器化环境之外,使用OpenJ9的类共享功能就像将JVM arg -Xshareclasss添加到启动脚本中并让OpenJ9的默认值处理其余部分一样简单。但是,在容器化环境中(如在云中运行Java应用程序时经常发生的情况),需要做更多的工作。让我们看一下在容器化环境中设置OpenJ9的类共享的两种方法,并权衡每种方法的优缺点。
方法1:使用Docker
将Docker卷用作共享类缓存直接来自OpenJ9 Docker页面。 设置共享类以使用Docker卷非常简单。
1.创建Docker。
2.这是我用来创建我的代码:
3.docker卷创建java共享类
4,在带有CMD或ENTRYPOINT的Dockerfile中,你需要启用类共享并显式定义存储类信息的位置:
5.ENTRYPOINT [“ java”,“ -Xshareclasses:cacheDir = / cache”,“ -Xscmx300M”,...]
6.-Xshareclasses可以有几个子选项,其中一个是cacheDir,这使我们可以显式定义用于存储类数据的目录。你还可以选择定义缓存的大小。 在这种情况下,我将缓存设置为非常大的300MB。
7.在运行Docker容器时必须装入该卷。 在Docker运行命令的脚本中,添加以下内容:
8.docker运行--mount source = java-shared-classes,target = / cache <映像名称>
9,重要的是,目标与Dockerfile中的-Xshareclasss中的cacheDir是同一目录。
注意: 你希望共享类缓存多大取决于许多因素,包括以下注意事项:
· ·是否仅在执行相同Java应用程序的容器之间共享它?
· ·运行Java应用程序的任何Docker容器都将使用缓存吗?
· ·Java应用程序之间有多少共同点?
请查阅-Xshareclasses上的OpenJ9用户文档,以获取有关运行和维护共享类缓存的实用程序方法的信息。
方法2:“预热” Docker容器
我的同事Mike Thompson向我介绍了“预热” Docker容器的方法。 他的原始代码可以在GitHub上找到。
通过预构建将在构建Docker映像时运行的Java应用程序来“预热” Docker容器。 通过使用-Xshareclasss执行Java应用程序,可以预填充缓存并将其存储在Docker映像中。 稍后运行Docker映像时,可以从预先存在的缓存中提取将要执行的Java应用程序。 为此,我们将使用Docker RUN命令:
RUN /bin/bash -c 'java -Xshareclasses -Xscmx20M -jar batch-processor-0.0.1-SNAPSHOT.jar --run_type=short &' ; sleep 15 ; xargs kill -1
Show more
设计RUN命令时有许多注意事项。 稍后我将对此进行更详细的介绍。 但首先,让我们比较一下使用Docker卷和预热Docker容器的性能。
性能比较
为了比较我之前描述的使用-Xshareclass的两种不同方法的性能,我使用了一个为OpenJ9演示创建的演示应用程序。 该演示是一个执行Spring Batch流程的Spring Boot应用程序,该流程正在对约200条记录进行转换。 如果你想了解有关应用程序正在执行的操作的更多详细信息,请查看我的项目的自述文件。
依次运行三个容器:
· “冷”容器是运行上述演示的Docker容器,但未打开类共享
· “卷”容器,这是使用外部卷存储类数据的Docker容器
· “热”容器使用上述方法预先用类数据预热Docker映像
每个图像运行了约30次,并收集了它们的运行时间和大内存使用量,并在下图中显示。 我们来看看图1和2中的图表,以了解如何比较类共享方法。
图1显示了执行批处理应用程序花费了多长时间的结果。
图1.折线图,比较了每个Docker容器在大约30次运行中的执行时间
“暖”和“冷”容器都有两个峰值。但是,总体而言,如趋势线所示,它们的性能是相当一致的。这是可以预料的,因为他们仅利用容器本身的内容。真正突出的是“体积”容器。
在我的演示中,我从一个空的缓存开始,这意味着“卷”容器不仅必须像“冷”容器那样编译类,而且还要将这些类写入缓存。图1中的图表显示了这些结果,从而导致相当大的吞吐量损失。 “冷”容器始终在大约9秒内完成执行,而“大容量”容器的初始运行花费了将近13秒。 (注意:未显示,但我运行了使用空的缓存多次启动“ volume”容器的场景,以验证上面看到的结果。)
但是,虽然“体积”容器在次运行中要慢得多,但第二次运行却与“温暖”容器一样快。这是有道理的,因为“热”容器在构建时也执行了一次批处理应用程序。
我终对每个“卷”容器执行了更多的执行。 “冷”和“暖”的性能保持相当一致,“容积”容器继续变得更好。这是因为每次执行容器时,OpenJ9都会向缓存添加更多的类信息和JIT优化。次执行的好处是巨大的,而第三次执行容器时还有另一个明显的颠簸(此后,好处却停滞了)。与“温暖”方法相比,“体积”方法随着时间的推移确实继续有所改善。
图2中的图表提供了更多有关权衡成本和缺点的分析,但让我们首先看看另一个性能因素:内存使用情况。
为了演示不同图像之间的内存使用差异,我使用了一个盒子和晶须图。在“晶须”中看到每次运行Docker容器时使用的大和小(换句话说,小的大)内存,而在“方框”中看到使用的中间50%内存。希望该图表显示了运行演示时发生的异常值,但也给出了预期的典型内存使用情况的想法。如此说来,让我们分析图2中的图表。
图2.盒子和胡须图,比较每个Docker容器的内存占用量
通过类共享,你将显着减少内存使用量。 如果没有类共享,则“冷”容器的内存将达到140MB左右,峰值可能高达240MB。 “热”和“大容量”容器的容量约为125MB,从未超过150MB。
权衡不同的方法
一种方法并不比另一种方法完全好,在确定应为组织使用哪种方法时,有许多因素需要考虑,其中有些因素不是上面描述的那么好,或者仅仅是在本地计算机上测试而已。让我们仔细考虑这些因素,以更好地了解这两种方法在更现实的情况下的行为。
我要介绍的个因素是延迟。我在本地计算机上运行了以上测试,因此我创建的卷与正在运行的Docker容器位于同一硬盘上。在云环境中,情况并非总是如此。卷的逻辑位置在物理上独立的计算机上时的额外延迟可能会对应用程序的启动时间产生显着影响。
对于使用预热的卷,还需要考虑几个因素。首先是与它们相关的额外存储开销。在我的示例中,预热的Docker容器的大小为390MB,而“冷”和“卷”对应的容器为369MB。根据你的组织作为Docker容器维护的服务数量以及图像保留的时间长短,可能会导致存储使用量显着增加。也就是说,适当的修剪做法可以轻松解决此问题。
要考虑的另一个因素是正确预热Docker容器可能需要一些工作。在我的演示中,我正在使用带有预定义数据集的Spring Batch作业,因此我只是使用RUN命令简单地执行了该作业的简化版本。但是,大多数应用程序不是批处理作业。对于这些,需要更多的工作来模拟负载。否则,当JIT初启动时,它不会在缓存中提供许多优化。
我认为这些因素都不是特别重要。如果你的性能标准非常严格,那么它们可能在很大程度上变得非常重要。但是,通常来说,这些因素是需要克服的相对较小的技术障碍。
结论
在学习OpenJ9的短时间内,我迷上了它。 与Hotspot的性能相匹配的大量内存节省使OpenJ9真正令人兴奋,并且交谈和编写都很有趣。
本文探讨的类共享可进一步节省内存并改善启动,并结合使用-Xquickstart等功能(有关更多信息,请参见OpenJ9用户文档),这使得OpenJ9对于无服务器功能非常有趣。 请继续关注,我计划在以后的文章中探讨后者。
> 喜欢这篇文章的可以点个赞,欢迎大家留言评论,记得关注我,每天持续更新技术干货、职场趣事、海量面试资料等等
> 如果你对java技术很感兴趣也可以交流学习,共同学习进步。
> 不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代
文章写道这里,欢迎完善交流。后奉上近期整理出来的一套完整的java架构思维导图,分享给大家对照知识点参考学习。有更多JVM、Mysql、Tomcat、Spring Boot、Spring Cloud、Zookeeper、Kafka、RabbitMQ、RockerMQ、Redis、ELK、Git等Java干货
相关文章