Class Sharing in Eclipse OpenJ9: How to Improve Memory, Performance (Part 2)
内存占用量和启动时间是Java虚拟机(JVM)的重要性能指标。 在云环境中,内存占用变得尤为重要,因为您需要为应用程序使用的内存付费。 在本教程中,我们将向您展示如何使用Eclipse OpenJ9中的共享类功能来减少内存占用并改善JVM启动时间。
运行时字节码修改
运行时字节码修改是一种将行为检测到Java类中的流行方法。 可以使用JVM工具接口(JVMTI)挂钩执行此操作(请在此处找到详细信息)。 或者,可以在定义类之前用类加载器替换类字节。 这给类共享带来了额外的挑战,因为一个JVM可能会缓存检测到的字节码,而该字节码不应由共享同一缓存的另一个JVM加载。
但是,由于OpenJ9 Shared Classes实现的动态性质,使用不同修改类型的多个JVM可以安全地共享同一缓存。 确实,如果字节码修改很昂贵,则缓存修改后的类会带来更大的好处,因为该转换只需执行一次即可。 唯一的规定是字节码修改应是确定性的和可预测的。 一旦修改并缓存了一个类,就无法再对其进行更改。
可以使用
潜在的陷阱
如果JVM与已注册用于修改类字节的JVMTI代理一起运行,并且未使用已修改的子选项,则与其他原始JVM或使用其他代理的JVM进行类共享仍将得到安全管理,尽管这样做会降低性能成本。 额外检查。 因此,使用修改后的子选项总是更有效。
请注意,这仅是可能的,因为JVM由于使用JVMTI API而知道何时进行字节码修改。 重新定义和重新转换的类不存储在缓存中。 JVM将原始类字节数据存储在共享高速缓存中,这允许为从高速缓存加载的所有类触发JVMTI
有关共享修改后的字节码的更多详细信息,请参见此处。
使用助手API
OpenJ9提供了Shared Classes Helper API,以便开发人员可以将类共享支持集成到自定义类加载器中。 这仅对于不扩展
关于Helper API的全面教程超出了本文的范围,但是我们将提供一个概述。 如果您想了解更多详细信息,可以在GitHub上找到Helper API实现。
助手API:摘要
所有Helper API类都在com.ibm.oti.shared包中。 每个希望共享类的类加载器必须从
使用SharedClassHelperFactory
使用SharedClassHelpers
工厂可以返回三种不同的
每个
在类加载器向其父类请求类(如果存在)之后,应调用
定义类后,应立即调用
其他注意事项
在与应用程序部署类共享时,您需要考虑诸如安全性和缓存调整之类的因素。 这些注意事项简要总结如下。
安全性
默认情况下,共享缓存是使用用户级安全性创建的,因此只有创建共享缓存的用户才能访问它。 因此,每个用户的默认缓存名称都不同,从而避免了冲突。 在UNIX上,有一个子选项可指定groupAccess,该选项可访问创建缓存的用户的主要组中的所有用户。
除此之外,如果安装了SecurityManager,则只有明确授予了正确的权限,类加载器才能共享类。 有关设置这些权限的更多详细信息,请参阅此处的用户指南。
垃圾收集和即时编译
在启用类共享的情况下运行对类垃圾回收(GC)无效。 就像在非共享情况下一样,类和类加载器仍会被垃圾回收。 此外,使用类共享时,对GC模式或配置也没有任何限制。
无法将即时(JIT)编译的代码缓存在类缓存中。 共享缓存中的AOT代码也需要进行JIT编译,并且会影响方法的方式和时间。 此外,JIT提示和配置文件数据可以存储在共享缓存中。 您可以使用选项
缓存大小限制
当前最大理论高速缓存大小为2GB。 缓存大小受诸如可用系统内存,可用虚拟地址空间,可用磁盘空间等因素的限制。有关更多详细信息,请参见此处。
示例
为了实际演示类共享的好处,本节提供了一个简单的图形演示。 源代码和二进制文件可在GitHub上获得。
该演示应用程序可在Java 8上运行,并查找
类加载效果
1.从Adopt OpenJDK项目中下载带有OpenJ9的JDK或从docker映像中提取。
2.从GitHub下载shcdemo.jar。
3.使用清单11中的命令,运行几次没有类共享的测试来预热系统磁盘缓存:
清单11.预热磁盘缓存
1 | C:\OpenJ9>wa6480_openj9\j2sdk-image\bin\java -Xshareclasses:none -cp shcdemo.jar ClassLoadStress |
当出现图1中的窗口时,按按钮。 该应用程序将加载类。
图1.按下按钮
一旦加载了类,应用程序将报告加载了多少以及花费了多长时间,如图2所示:
图2.结果在!
您会注意到,每次运行该应用程序时,它的运行速度可能都会有所提高。 这是因为操作系统优化。
4.现在,运行启用了类共享的演示,如清单12所示。 创建一个新的共享缓存。 您可以指定大约50MB的缓存大小,以确保所有类都有足够的空间。 清单12显示了命令行和一些示例输出。
清单12.在启用类共享的情况下运行演示
1 2 3 4 5 6 7 8 | C:\OpenJ9>wa6480_openj9\j2sdk-image\bin\java -cp shcdemo.jar -Xshareclasses:name=demo,verbose -Xscmx50m ClassLoadStress [-Xshareclasses persistent cache enabled] [-Xshareclasses verbose output enabled] JVMSHRC236I Created shared classes persistent cache demo JVMSHRC246I Attached shared classes persistent cache demo JVMSHRC765I Memory page protection on runtime data, string read-write data and partially filled pages is successfully enabled JVMSHRC168I Total shared class bytes read=1111375. Total bytes stored=40947096 JVMSHRC818I Total unstored bytes due to the setting of shared cache soft max is 0. Unstored AOT bytes due to the setting of -Xscmaxaot is 0. Unstored JIT bytes due to the setting of -Xscmaxjitdata is 0. |
您还可以使用
清单13.检查缓存类的数量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | C:\OpenJ9>wa6480_openj9\j2sdk-image\bin\java -cp shcdemo.jar -Xshareclasses:name=demo,printStats Current statistics for cache"demo": Cache created with: -Xnolinenumbers = false BCI Enabled = true Restrict Classpaths = false Feature = cr Cache contains only classes with line numbers base address = 0x0000000011F96000 end address = 0x0000000015140000 allocation pointer = 0x000000001403FF50 cache size = 52428192 softmx bytes = 52428192 free bytes = 10874992 ROMClass bytes = 34250576 AOT bytes = 1193452 Reserved space for AOT bytes = -1 Maximum space for AOT bytes = -1 JIT data bytes = 28208 Reserved space for JIT data bytes = -1 Maximum space for JIT data bytes = -1 Zip cache bytes = 902472 Data bytes = 351648 Metadata bytes = 661212 Metadata % used = 1% Class debug area size = 4165632 Class debug area used bytes = 3911176 Class debug area % used = 93% # ROMClasses = 17062 # AOT Methods = 559 # Classpaths = 3 # URLs = 0 # Tokens = 0 # Zip caches = 5 # Stale classes = 0 % Stale classes = 0% Cache is 79% full Cache is accessible to current user = true |
5.现在,使用相同的Java命令行再次启动演示。 这次,它应该从共享类缓存中读取类,如清单14所示。
清单14.使用热共享缓存运行应用程序
1 2 3 4 5 6 7 8 | C:\OpenJ9>wa6480_openj9\j2sdk-image\bin\java -cp shcdemo.jar -Xshareclasses:name=demo,verbose -Xscmx50m ClassLoadStress [-Xshareclasses persistent cache enabled] [-Xshareclasses verbose output enabled] JVMSHRC237I Opened shared classes persistent cache demo JVMSHRC246I Attached shared classes persistent cache demo JVMSHRC765I Memory page protection on runtime data, string read-write data and partially filled pages is successfully enabled JVMSHRC168I Total shared class bytes read=36841382. Total bytes stored=50652 JVMSHRC818I Total unstored bytes due to the setting of shared cache soft max is 0. Unstored AOT bytes due to the setting of -Xscmaxaot is 0. Unstored JIT bytes due to the setting of -Xscmaxjitdata is 0. |
从图3可以清楚地看到类加载时间的显着改善(大约40%)。由于操作系统的优化,每次运行演示时,性能应该都会略有提高。
图3.热缓存结果
您可以尝试一些变体。 例如,您可以使用javaw命令启动多个演示并一起触发所有加载类以查看并发性能。
在实际情况下,使用类共享可以获得的总体JVM启动时间收益取决于应用程序加载的类数。 HelloWorld程序不会带来太大好处,而大型Web服务器当然可以。 但是,该示例有望证明,尝试类共享非常简单,因此您可以轻松地测试其好处。
内存占用
在多个JVM中运行示例程序时,也很容易看到节省的内存。
下面是使用与前面的示例相同的计算机获得的四个VMMap快照。 在图4中,该演示的两个实例已经运行完毕而没有类共享。 在图5中,使用与以前相同的命令行,在启用类共享的情况下运行了两个实例以完成操作。
图4.两个没有类共享的演示实例
图5.启用了类共享的两个演示实例
在实验中,共享缓存的大小为50MB,因此与图5相比,图6中每个实例的"映射文件"大小增加了50MB(56736KB – 5536KB)。
您可以清楚地看到启用共享类时的内存使用量(私有WS)明显较低。 对于2个JVM实例,可以节省大约70MB的私有WS。 如果在启用了类共享的情况下启动了更多的演示实例,则会节省更多的内存。 上面的测试结果是在具有32GB RAM的Windows 10笔记本电脑上,使用Intel?Core?i7-6820HQ CPU @ 2.70GHz获得的。
我们也在Linux x64机器上执行相同的内存占用量实验。 清单15显示了两个没有类共享的JVM实例的结果,清单16显示了两个启用了类共享的JVM实例的结果。
从结果来看,启用类共享时,RSS并没有显示太多改进。 这是因为整个共享缓存都包含在RSS中。 但是,如果我们看一下PSS,该PSS仅占每个JVM共享缓存的一半(因为它由2个JVM共享),则节省了大约34MB。
清单15.禁用类共享的Linux上的足迹
1 2 3 4 5 6 7 8 9 10 11 12 | pmap -X 9612 9612: xa6480_openj9/j2sdk-image/jre/bin/java -cp shcdemo.jar ClassLoadStress Address Perm … Size Rss Pss Referenced Anonymous Swap Locked Mapping … ======= ======= ===== ======== ========= ==== ==== 2676500 118280 106192 118280 95860 0 0 KB pmap -X 9850 9850: xa6480_openj9/j2sdk-image/jre/bin/java -cp shcdemo.jar ClassLoadStress Address Perm … Size Rss Pss Referenced Anonymous Swap Locked Mapping … ======= ======= ===== ======== ========= ==== ==== 2676500 124852 112792 124852 102448 0 0 KB |
清单16.启用类共享的Linux上的足迹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | pmap -X 4501 4501: xa6480_openj9/j2sdk-image/jre/bin/java -Xshareclasses:name=demo -Xscmx50m -cp shcdemo.jar ClassLoadStress Address Perm … Size Rss Pss Referenced Anonymous Swap Locked Mapping … 7fe7d0e00000 rw-s 4 4 2 4 0 0 0 C290M4F1A64P_demo_G35 7fe7d0e01000 r--s 33356 33356 16678 33356 0 0 0 C290M4F1A64P_demo_G35 7fe7d2e94000 rw-s 11096 48 24 48 0 0 0 C290M4F1A64P_demo_G35 7fe7d396a000 r--s 5376 1640 832 1640 0 0 0 C290M4F1A64P_demo_G35 7fe7d3eaa000 rw-s 296 0 0 0 0 0 0 C290M4F1A64P_demo_G35 7fe7d3ef4000 r--s 1072 0 0 0 0 0 0 C290M4F1A64P_demo_G35 … ======= ======= ===== ======== ====== ====== ==== 2732852 120656 90817 97988 62572 0 0 KB pmap -X 4574 4574: xa6480_openj9/j2sdk-image/jre/bin/java -Xshareclasses:name=demo -Xscmx50m -cp shcdemo.jar ClassLoadStress Address Perm … Size Rss Pss Referenced Anonymous Swap Locked Mapping … 7f308ce00000 rw-s 4 4 2 4 0 0 0 C290M4F1A64P_demo_G35 7f308ce01000 r--s 33356 33356 16678 33356 0 0 0 C290M4F1A64P_demo_G35 7f308ee94000 rw-s 11080 48 24 48 0 0 0 C290M4F1A64P_demo_G35 7f308f966000 r--s 5392 1632 824 1632 0 0 0 C290M4F1A64P_demo_G35 7f308feaa000 rw-s 296 0 0 0 0 0 0 C290M4F1A64P_demo_G35 7f308fef4000 r--s 1072 0 0 0 0 0 0 C290M4F1A64P_demo_G35 … ======= ======= ===== ======== ====== ====== ==== 2730800 122832 92911 102584 64812 0 0 KB |
结论
OpenJ9实现中的"共享类"功能提供了一种简单而灵活的方式来减少内存占用并缩短JVM启动时间。 在本文中,您已经了解了如何启用该功能,如何使用缓存实用程序以及如何对收益进行量化。