共享类基础结构的增强功能,首先在IBM JRE版本5中针对Java平台SE引入,可在启动和内存占用方面提高Java应用程序的性能。 在本文中,我们回顾了这些变化,并提供了示例,展示了使用Eclipse和Apache Tomcat作为客户端和服务器端操作环境的好处。 我们提供了一些安装说明,以便您可以自己尝试一下,但是您应该熟悉这两个应用程序以及IBM的类共享。 如果您不熟悉IBM的共享类功能,建议您从文章“ Java技术,IBM风格:类共享 ”开始,它解释了所涉及的基本概念。
如果您想继续阅读本示例,请立即下载IBM JRE for Java 6 forLinux®和AIX®的实现。 虽然Windows®实现不能单独下载,但它是预构建的Eclipse下载。 请注意,需要IBM注册(免费)。
IBM共享类中有哪些新内容?
IBM JRE for Java 5使您能够在具有缓存的JVM之间共享类。 在IBM JRE for Java 6中,您可以使高速缓存持久化,并可以使用它共享已编译的代码。 存储这些缓存项的方法也变得更加有效。
共享类
在Java JRE for Java 5中首次引入了在Java虚拟机(JVM)之间共享类的功能,并在Java 6中继续得到支持和增强。通过JVM加载类时,可以将它们放置在高速缓存中。 发出对该类的后续请求时,如果可能,可以从缓存中满足这些请求,而不是从相应的JAR文件中加载该类。
您可以使用清单1中的命令行选项来控制高速缓存的最大大小,但是请注意,此最大大小可能受到共享内存的操作系统限制的约束:
清单1.设置最大缓存大小的命令行选项
Running java -X will show the following option ...
Arguments to the following options are expressed in bytes.
Values suffixed with "k" (kilo) or "m" (mega) will be factored accordingly.
:
-Xscmx<x> set size of new shared class cache to <x>
:
提前(AOT)代码存储
JVM通常在程序执行时将Java方法编译为本地代码。 每次运行程序时都会生成此本地代码。 IBM JRE for Java 6 SR1 JVM引入了使用Ahead of Time编译技术来编译Java方法的功能,以创建本机代码,这些本机代码不仅可以在当前JVM中使用,还可以存储在共享类高速缓存中。 使用共享类高速缓存启动的另一个JVM已在先前的Java程序调用期间填充了AOT代码,可以使用存储在高速缓存中的AOT代码来减少启动时间。 通过节省编译所需的时间和更快地执行作为AOT代码的方法,可以实现这种减少。 AOT代码是本机代码,通常比解释代码执行得更快(尽管它的运行速度可能不及JIT生成的代码快)。
可以使用命令行选项来定义AOT代码可以占用的共享类高速缓存的最小和最大数量,如清单2所示。如果您未指定可以存储的最大AOT代码数量,则默认为设置是使用整个缓存。 但是,这不会导致整个缓存都充满AOT代码,因为AOT代码只能从缓存中已经存在的类中生成。
清单2.用于控制缓存的AOT代码的大小的命令行选项
Running java -X will show the following options ...
Arguments to the following options are expressed in bytes.
Values suffixed with "k" (kilo) or "m" (mega) will be factored accordingly.
:
-Xscminaot<x> set minimum shared classes cache space reserved for AOT data to <x>
-Xscmaxaot<x> set maximum shared classes cache space allowed for AOT data to <x>
图1说明了共享类和AOT代码如何占用高速缓存空间,以及高速缓存空间设置如何控制这些空间使用总可用空间的份额:
图1.样本共享类缓存组成
不久将了解有关AOT代码的更多信息。
类压缩
为了最有效地使用共享类高速缓存,JVM使用压缩技术来增加可以存储的类的数量。 类压缩是自动完成的,不能通过使用命令行选项进行更改。
永久缓存
IBM JRE for Java 5中的共享类高速缓存是使用共享内存段实现的,该共享内存段允许JVM共享相同的高速缓存,但是该高速缓存无法在操作系统重新启动后持续存在。 这意味着在重新引导后启动的第一个JVM必须重建缓存。 在Java 6中,缓存的默认实现已更改为使用内存映射文件。 这可在整个操作系统重新启动时提供缓存持久性。
AOT详细
AOT编译器是IBM JRE for Java 6中新增的一种附加编译机制。使用IBM JRE的早期版本,可以通过解释构成该方法的每个单独的Java字节码来执行Java方法,也可以通过以下方式来执行该方法:作为本地机器代码执行,由称为即时(JIT)编译器的JRE组件进行编译和优化。 JIT动态地编译代码。 该方法实际运行时会发生编译过程,并且所采用的编译技术取决于在执行过程中对实际方法的分析。
什么是AOT代码?
AOT代码是AOT编译产生的Java方法的本机代码版本。 与JIT编译不同,AOT编译不基于对执行中的Java方法进行动态分析的优化。 通常,一种方法作为AOT编译的本机代码执行的速度将比解释的Java字节代码执行得更快,但不如JIT编译的本机代码执行得那么快。
AOT编译的主要目标是通过提供Java方法的预编译版本来加速应用程序的启动。 与生成JIT编译的代码相比,从共享类缓存中加载这些预编译的AOT方法是使Java方法的本机代码版本可用于执行的更快的方法。 快速加载AOT编译的代码使JVM在提供本机代码版本可用之前花费较少的时间来解释Java方法。 AOT编译的方法也需要进行JIT编译,因此在最初作为AOT代码执行后,可以通过JIT对其进行进一步优化。
AOT作为共享类的一部分
生成的AOT代码存储在共享缓存的区域中。 随后,使用该共享类缓存的任何其他JVM都可以将该方法作为AOT代码执行,而不会产生编译成本。
此实现不同于实时JVM,在实时JVM中,AOT代码的编译是由实用程序(jxeinajar)执行并存储在jar文件中的,如“ 实时Java,第1部分:使用Java语言进行实时”中所述。系统 。”
JVM执行的AOT代码不是共享的,而是从共享类高速缓存中复制出来的。 没有直接的占用空间收益,因为每个JVM仍然具有AOT可执行文件的副本,但是由于能够重复使用此代码而不是重复编译,因此节省了内存和CPU。
AOT诊断
可以使用三种命令行设置来帮助您了解应用程序已经使用AOT编译了哪些方法以及这些方法在共享类缓存中占用了多少空间:
-
-Xjit:verbose
:使用此命令报告由JIT执行的任何AOT编译。 -
-Xshareclasses:verboseAOT
:使用此命令报告从共享类高速缓存中读取或存储的所有AOT代码。 -
java -Xshareclasses:printAllStats
:使用此命令可以列出共享类缓存的统计信息,包括存储的AOT代码和占用的空间。
清单3显示了清除共享类缓存并应用运行时选项-Xjit:verbose
和-Xshareclasses:verboseAOT
之后, 第一次调用Tomcat服务器的输出:
清单3.应用-Xjit:verbose
和-Xshareclasses:verboseAOT
+ (AOT cold) java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
Storing AOT code for ROMMethod 0x02359850 in shared cache... Succeeded.
+ (AOT cold) sun/misc/URLClassPath$JarLoader.ensureOpen()V @ 0x0147BF9C-0x0147C106 Q_SZ=3
Storing AOT code for ROMMethod 0x023CBFC4 in shared cache... Succeeded.
+ (AOT cold) java/util/jar/JarFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;
Storing AOT code for ROMMethod 0x023CE38C in shared cache... Succeeded.
初始化Tomcat服务器之后,使用命令java -Xshareclasses:printAllStats
获得的共享类缓存统计信息表明这些方法存储在共享类缓存中(清单4是完整清单的摘录):
清单4.使用java -Xshareclasses:printAllStats
显示存储在共享类缓存中的方法
1: 0x43469B8C AOT: append
for ROMClass java/lang/StringBuilder at 0x42539178.
1: 0x43469634 AOT: ensureOpen
for ROMClass sun/misc/URLClassPath$JarLoader at 0x425AB758.
1: 0x434693A8 AOT: getEntry
for ROMClass java/util/jar/JarFile at 0x425ADAD8.
如清单5所示,随后使用共享类缓存对Tomcat服务器的调用将发现这些方法已经被AOT编译,并且将仅从缓存中加载这些方法,而无需重复编译:
清单5.查找和加载AOT编译的方法
Finding AOT code for ROMMethod 0x02359850 in shared cache... Succeeded.
(AOT load) java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
Finding AOT code for ROMMethod 0x023CBFC4 in shared cache... Succeeded.
(AOT load) sun/misc/URLClassPath$JarLoader.ensureOpen()V
Finding AOT code for ROMMethod 0x023CE38C in shared cache... Succeeded.
(AOT load) java/util/jar/JarFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;
AOT编译会做出启发式决策,以选择候选方法,以缩短未来的启动时间。 因此,将来对应用程序的调用也可能会导致一些其他方法被AOT编译。
如果AOT编译的方法符合必要的重新编译标准,则也可以JIT编译。 但是,AOT编译的目的是选择应用程序启动时所需的方法,而JIT编译的目的是优化常用方法,因此,有可能无法随后调用经AOT编译的方法来触发JIT。汇编。
清单6是使用-Xjit:verbose
输出的摘录,用于执行SPECjbb2005基准测试。 该摘录包含两种方法的AOT编译报告: com/ibm/security/util/ObjectIdentifier.equals
和java/math/BigDecimal.multiply
。 第一个不受进一步JIT编译的约束,但是更频繁使用的java/math/BigDecimal.multiply
两次JIT编译,最终达到了最热的优化级别。
SPECjbb2005没有冗长的启动阶段,因此仅编译了几种AOT方法。 请注意,AOT编译是在冷优化级别执行的,这反映了加速应用程序启动的总体AOT目标。
清单6.使用-Xjit:verbose
时报告的-Xjit:verbose
+ (AOT cold) com/ibm/security/util/ObjectIdentifier.equals(Ljava/lang/Object;)
Storing AOT code for ROMMethod 0x118B8AF4 in shared cache... Succeeded.
+ (AOT cold) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
Storing AOT code for ROMMethod 0x119D3C60 in shared cache... Succeeded.
+ (warm) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
+ (hot) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
命令java -Xshareclasses:printAllStats
生成的共享类统计信息列出了每个AOT编译的方法和每个缓存的共享类。 我们可以使用此摘要数据来了解共享类缓存的大小是否正确。 例如,清单7显示总的高速缓存大小为16776844字节,仅占40%,并且5950936字节来自1668 ROMClass,而683772字节来自458 AOT编译的方法:
清单7.缓存详细信息
base address = 0x424DE000
end address = 0x434D0000
allocation pointer = 0x4295E748
cache size = 16776844
free bytes = 9971656
ROMClass bytes = 5950936 AOT bytes = 683772
Data bytes = 57428
Metadata bytes = 113052
Metadata % used = 1%
# ROMClasses = 1668# AOT Methods = 458
# Classpaths = 7
# URLs = 0
# Tokens = 0
# Stale classes = 0
% Stale classes = 0%
Cache is 40% full
清单8,命令-Xshareclasses:destroyAll
产生的输出指示已销毁的缓存。 该命令还导致消息无法创建Java虚拟机,因此不会发出警报。 它做到了。
清单8.销毁缓存
Attempting to destroy all caches in cacheDir C:\...\javasharedresources\
JVMSHRC256I Persistent shared cache "eclipse" has been destroyed
Could not create the Java virtual machine.
测量内存使用率
您可以使用许多性能工具来查看共享类在内存使用方面的好处。 您使用的特定工具取决于基础操作系统。 在检查内存使用情况时要记住的一件事是,缓存是由一个内存映射文件提供的,该文件允许其内容由多个虚拟机共享。 用于确定内存使用情况的任何工具都必须能够区分共享内存(可以由多个JVM访问和共享)和私有内存(只能由单个JVM访问)。
虚拟地址转储实用程序(Windows)
虚拟地址转储实用程序或vadump是Microsoft®资源工具包的一部分,可用于提供有关应用程序或共享类缓存的内存使用情况的信息。 Vadump会产生很多信息,但是我们将仅查看报告的数字以了解工作集大小,以使我们了解应用程序的内存使用情况。 命令vadump -os -p <pid>
显示给定进程ID的工作集。
产生的摘要信息包含许多有关进程使用的内存的信息。 为了显示使用共享类实现的内存改进,我们重点关注“ 总计工作集”图以及类数据共享如何影响此总体图的Private , Shareable和Shared贡献。 清单9显示了vadump的摘要输出示例。 共享类被实现为内存映射文件,因此它们占用的内存在“ 映射数据”输出行中显示。
清单9.示例vadump输出
vadump -os -p 5364
Category Total Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 29 116 116 0 0
Other System 8 32 32 0 0
Code/StaticData 2079 8316 5328 140 2848
Heap 87 348 348 0 0
Stack 4 16 16 0 0
Teb 1 4 4 0 0
Mapped Data 95 380 0 24 356
Other Data 61 244 240 4 0
Total Modules 2079 8316 5328 140 2848
Total Dynamic Data 248 992 608 28 356
Total System 37 148 148 0 0
Grand Total Working Set 2364 9456 6084 168 3204
要确定vadump命令中所需的进程ID,可以使用Windows任务管理器:
- 打开任务管理器应用程序,然后选择“ 进程”选项卡。
- 找到显示的名为PID的列。 (如果未显示此列,请单击“ 视图”>“选择列”,然后选择“ PID”复选框,如图2所示。)
- 找到要分析的进程的名称,并记下“ PID”列中的值。 这是您需要传递给vadump的进程ID。
图2.在任务管理器中启用进程ID信息
使用top衡量Linux上的内存使用情况
对于想要检查内存使用情况的Linux用户,存在许多工具。 top
命令适合我们的目的,用于显示共享类的效果。 为了使输出更易于阅读,我们将在命令行上提供进程ID并以批处理模式运行它。 清单10显示了命令行和接收到的数据的示例:
清单10.顶部命令行和示例输出
top -b -n 1 -p <pid>
top - 13:33:41 up 18 days, 9:30, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0% us, 0.0% sy, 0.0% ni, 100.0% id, 0.0% wa, 0.0% hi, 0.0% si
Mem: 8157972k total, 311312k used, 7846660k free, 56448k buffers
Swap: 2104472k total, 0k used, 2104472k free, 141956k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7073 root 15 0 41616 13m 2228 S 0.0 0.2 5:43.70 X
以下是我们最感兴趣的值:
- VIRT —虚拟映像(KB) :任务使用的虚拟内存总量。 它包括所有代码,数据和共享库以及已换出的页面。
- RES —驻留大小(KB) :任务已使用的未交换物理内存。
- SHR —共享内存大小(KB) :任务使用的共享内存量。 它只是反映了可能与其他进程共享的内存。
配置Eclipse以使用共享类功能
为了说明可以使用共享类实现的占用空间和启动方面的改进,我们将测量对两个实际应用程序的影响,Eclipse是代表客户端的桌面应用程序,而Apache Tomcat是代表客户端的桌面应用程序。服务器端应用程序。
正如我们在本文开头所述,目前没有独立安装Java 6的IBM SDK和Windows的Java平台的运行时。 如果您使用的是Windows(而不是Linux或AIX),则需要下载Eclipse的预捆绑软件包 。
如果您使用的是Linux或AIX ,请下载用于Java 6实施的独立IBM SDK,然后从Eclipse项目站点下载所需的Eclipse版本(请参阅参考资料 )。 按照Eclipse安装说明进行操作,以使Eclipse使用IBM Java SDK 6。
安装Eclipse之后,您需要执行以下附加步骤:
- 启用插件的类共享。 将OSGI插件适配器(请参阅参考资料 )安装到Eclipse插件目录中。
- 下载SampleView.jar(请参阅下载 )并将其安装到Eclipse插件目录中。 通过插入到IBM JVM跟踪中,然后在初始化视图时输出一些跟踪点,此插件使您更容易地计时Eclipse的启动时间。 下一节将介绍如何使用IBM JVM跟踪来提供启动统计信息。
- 创建两个名为workspace1和workspace2的工作空间 。 这使您可以启动两个Eclipse实例,它们指向不同的工作空间,但它们共享相同的类高速缓存。
如果还没有的话,还需要设置Tomcat。 只需从Apache Tomcat网站下载该应用程序,解压缩该软件包,然后按照running.txt文件中包含的说明进行操作。
性能比较
使用前面各节中描述的工具和应用程序,我们将测量共享类提供的性能提升。 在可能的情况下,我们尝试隔离共享类功能(通过在可能的情况下禁用其他功能),以便更轻松地解释结果。
Eclipse性能:足迹
为了检查占用空间,我们使用不同的工作空间在同一Windows系统上同时运行了多个Eclipse实例。 我们收集了vadump数据以使用Eclipse启动的三种不同模式进行比较:
- Eclipse正常启动,没有启用任何共享类功能。
- Eclipse首次使用已清除的共享类缓存启动。
- 使用与上面相同的共享类缓存来启动第二个Eclipse实例。
要使用Eclipse启用共享类,我们需要创建一个新的启动命令行,其中包含正确的JVM选项。 清单11展示了一个用来启动Eclipse的批处理文件,而不仅仅是创建一个新的快捷方式。 它执行以下功能:
- 采用单个命令行参数1或2,该参数与配置Eclipse时创建的工作区相对应。
- 如果指定了工作空间1,则清除所有已经存在的共享类缓存。
- Eclipse终止后,将打印缓存的统计信息。
清单11.用于启动Eclipse的批处理文件
@echo off
rem batch file to start Eclipse using the specified workspace
SET ECLIPSE_HOME=C:\java\eclipse\IBMEclipse\eclipse
SET JVM=C:\java\eclipse\IBMEclipse\ibm_sdk50\jre\bin\java.exe
SET WNAME=C:\java\eclipse\workspace%1
SET SC_OPTS=-Xshareclasses:name=eclipse,verbose
SET VMARGS=%SC_OPTS%
echo Clearing shared classes cache
if %1==1 %JVM% -Xshareclasses:destroyAll
echo JVM version
%JVM% -version
echo Starting Eclipse
%ECLIPSE_HOME%\eclipse.exe -nosplash -data %WNAME% -vm %JVM% -vmargs %VMARGS%
%JVM% -Xshareclasses:name=eclipse,printStats
清单12显示了不共享类的Eclipse实例的vadump报告。 vadump输出中我们最感兴趣的字段是“可共享的KB”,“共享的KB”和“总计工作集:千字节”。
清单12.没有共享类的Eclipse的vadump输出
Category Total Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 54 216 216 0 0
Other System 28 112 112 0 0
Code/StaticData 4199 16796 11500 1052 4244
Heap 9400 37600 37600 0 0
Stack 98 392 392 0 0
Teb 21 84 84 0 0
Mapped Data 130 520 0 36 484
Other Data 5337 21348 21344 4 0
Total Modules 4199 16796 11500 1052 4244
Total Dynamic Data 14986 59944 59420 40 484
Total System 82 328 328 0 0
Grand Total Working Set 19267 77068 71248 1092 4728
清单13显示了使用清单11中的批处理文件启动Eclipse时vadump的输出,您可以看到大约4MB的类(报告为4116 KB的可共享映射数据)已放入缓存中,这增加了整体工作集大小相应减少。 高亮显示的条目表明该内存已被标记为可以由其他进程共享。 比较vadump的输出时要记住的一件事是,尽管它们是在Eclipse启动时获取的,但是报告的数字会有一些小的变化。
清单13.第一次使用共享类缓存启动Eclipse的vadump输出和统计信息
Category Total Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 54 216 216 0 0
Other System 28 112 112 0 0
Code/StaticData 4256 17024 11676 1072 4276
Heap 8631 34524 34524 0 0
Stack 103 412 412 0 0
Teb 20 80 80 0 0
Mapped Data 1155 4620 0 4116 504
Other Data 5386 21544 21540 4 0
Total Modules 4256 17024 11676 1072 4276
Total Dynamic Data 15295 61180 56556 4120 504
Total System 82 328 328 0 0
Grand Total Working Set 19633 78532 68560 5192 4780
Current statistics for cache "eclipse":
base address = 0x42B0E000
end address = 0x43B00000
allocation pointer = 0x42E0B958
cache size = 16776844
free bytes = 12005976
ROMClass bytes = 4001256
AOT bytes = 625428
Data bytes = 57043
Metadata bytes = 87141
Metadata % used = 1%
# ROMClasses = 1334
# AOT Methods = 480
# Classpaths = 4
# URLs = 0
# Tokens = 0
# Stale classes = 0
% Stale classes = 0%
启动Eclipse的另一个实例,然后在该实例上运行vadump,如清单14所示,最初似乎在内存使用上几乎没有什么区别。 但是仔细检查后,您会发现4MB内存(报告为4564 KB的共享映射数据)实际上正在与另一个进程共享。 Vadump(和任务管理器)为使用它的每个进程在“总计工作集”中计数共享内存。 因此,第二个Eclipse实例的占用空间减少了4MB,因为它共享了第一个Eclipse实例创建和填充的类缓存。
显示的结果是在安装了最少数量的插件的情况下启动Eclipse的结果。 如果安装了更多插件,则可以期望将更多类放置在共享类缓存中,并且可以相应地缩短启动时间。
清单14.使用现有的共享类缓存启动第二个Eclipse的vadump输出
Category Total Private
Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 54 216 216 0 0
Other System 29 116 116 0 0
Code/StaticData 4254 17016 11676 0 5340
Heap 8684 34736 34736 0 0
Stack 98 392 392 0 0
Teb 20 80 80 0 0
Mapped Data 1150 4600 0 36 4564
Other Data 5261 21044 21040 4 0
Total Modules 4254 17016 11676 0 5340
Total Dynamic Data 15213 60852 56248 40 4564
Total System 83 332 332 0 0
Grand Total Working Set 19550 78200 68256 40 9904
Eclipse性能:启动
除了减少占用空间外,共享类还减少了启动时间,因为类是从缓存而不是从磁盘加载的。 此外,使用缓存中的AOT代码有助于减少启动时间。 为了计时Eclipse的启动时间,我们使用了一个定制视图( 本文前面已经描述过),该视图利用IBM JVM跟踪在加载时写出消息。 我们还必须修改清单11中的Eclipse启动批处理文件,以启用JVM跟踪并记录以下跟踪事件:
- 跟踪的初始化:跟踪几乎在启动JVM之后但在加载任何类之前立即开始。 我们以此为起点来比较启动时间。
- 示例视图消息:初始化视图时,第一条消息被写出,用于指示Eclipse已经启动。 我们以此为终点来比较启动时间。
清单15显示了修改后的批处理文件,其他的JVM跟踪配置行以粗体显示:
清单15.批处理文件以启动启用跟踪的Eclipse
@echo off
rem batch file to time Eclipse startup
SET ECLIPSE_HOME=C:\java\eclipse\IBMEclipse\eclipse
SET WNAME=C:\java\eclipse\workspace%1
SET JVM=C:\java\eclipse\IBMEclipse\ibm_sdk60\jre\bin\java.exe
SET TRACE_OPTS=-Xtrace:iprint=tpnid{j9trc.0},iprint=SampleView
SET SC_OPTS=-Xshareclasses:name=eclipse,verbose
SET VMARGS=%SC_OPTS% %TRACE_OPTS%
echo Clearing shared classes cache
if %1==1 %JVM% -Xshareclasses:destroyAll
echo JVM version
%JVM% -version
echo VM arguments
echo %VMARGS%
echo Starting Eclipse
%ECLIPSE_HOME%\eclipse.exe -nosplash -data %WNAME% -vm %JVM% -vmargs %VMARGS%
%JVM% -Xshareclasses:name=eclipse,printStats
清单16和17展示了在没有共享类的情况下启动Eclipse并启用它们时的输出。 如您所见,启动时间缩短了将近1秒,这意味着启动时间减少了25%。 使用共享类运行时显示的时间是Eclipse的第二次启动,因为第一个将用于填充缓存。 使用Eclipse的裸露版本,缓存中仅存储4MB数据,因此,更大,更复杂的基于Eclipse的应用程序有足够的空间来利用类共享来减少启动时间。
清单16. Eclipse启动,没有共享的类
09:47:55.296*0x41471300 j9trc.0 - Trace initialized for VM = 00096238
09:47:59.500 0x41471300SampleView.2 - Event id 1, text = Mark
09:47:59.500 0x41471300SampleView.0 > Entering getElements(Object parent)
09:47:59.500 0x41471300SampleView.1 < Exiting getElements(Object parent)
Startup = 4.204 seconds
清单17. Eclipse启动,启用了共享类
09:30:40.171*0x41471300 j9trc.0 - Trace initialized for VM = 000962A8
[-Xshareclasses verbose output enabled]
JVMSHRC158I Successfully created shared class cache "eclipse"
JVMSHRC166I Attached to cache "eclipse", size=16777176 bytes
09:30:43.484 0x41471300SampleView.2 - Event id 1, text = Mark
09:30:43.484 0x41471300SampleView.0 > Entering getElements(Object
parent) 09:30:43.484 0x41471300SampleView.1 < Exiting
getElements(Object parent)
Startup = 3.313 seconds
Tomcat性能:足迹
到目前为止,我们已经看到共享类如何在客户端环境中提供启动和占用空间方面的改进。 这些好处也可以在服务器端环境中实现。 如前所述,我们使用Tomcat作为示例应用程序。 Tomcat不需要任何特殊步骤即可使用IBM JVM。 充分利用共享类的唯一步骤是为JVM_OPTS环境变量设置适当的值,如清单18所示,Tomcat使用该值通过特定的命令行选项启动其JVM:
清单18.为Tomcat设置JVM选项
export JAVA_OPTS="-Xmx32m -Xms32m -Xshareclasses:name=tomcat,verbose"
为了演示跨不同平台的共享类的效果,我们使用了IBM JVM和Tomcat的Linux版本。
如前所述, top
命令是衡量Linux上Tomcat占用空间的好工具。 在此示例中,当我们在未启用共享类的情况下启动Tomcat(通过从JVM_OPTS环境变量中删除“ -Xshareclasses:name = tomcat,verbose”)并再次启用共享类的情况下,我们排名top
。 然后,我们启动了Tomcat的第二个实例,以演示针对共享同一类高速缓存的2个Tomcat进程报告的内存使用差异。 清单19、20和21显示了每种情况下的top
输出。 清单22显示了适当时共享类的缓存统计信息。
清单19.没有共享类的Tomcat占用空间
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.1% us, 0.0% sy, 0.0% ni, 99.9% id, 0.0% wa, 0.0% hi, 0.0% si
Mem: 8157972k total, 1727072k used, 6430900k free, 101152k buffers
Swap: 2104472k total, 0k used, 2104472k free, 1370944k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24595 jbench 25 0 66744 54m 8400 S 0.0 0.7 0:03.71 java
清单20.带有共享类的Tomcat足迹
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0% us, 0.0% sy, 0.0% ni, 99.9% id, 0.1% wa, 0.0% hi, 0.0% si
Mem: 8157972k total, 1728800k used, 6429172k free, 101152k buffers
Swap: 2104472k total, 0k used, 2104472k free, 1376084k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24621 jbench 17 0 78440 56m 14m S 0.0 0.7 0:04.04 java
清单21.共享相同类缓存的Tomcat的2个实例的足迹
Tasks: 2 total, 0 running, 2 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0% us, 0.0% sy, 0.0% ni, 100.0% id, 0.0% wa, 0.0% hi, 0.0% si
Mem: 8157972k total, 1766440k used, 6391532k free, 101152k buffers
Swap: 2104472k total, 0k used, 2104472k free, 1376084k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24621 jbench 17 0 78440 56m 14m S 0.0 0.7 0:04.08 java
24674 jbench 16 0 77600 51m 14m S 0.0 0.6 0:02.28 java
清单22. Tomcat使用的缓存的当前统计信息
base address = 0x76D0E000
end address = 0x77D00000
allocation pointer = 0x77186268
cache size = 16776852
free bytes = 10085680
ROMClass bytes = 5911028
AOT bytes = 621280
Data bytes = 57051
Metadata bytes = 101813
Metadata % used = 1%
# ROMClasses = 1634
# AOT Methods = 452
# Classpaths = 6
# URLs = 0
# Tokens = 0
# Stale classes = 0
% Stale classes = 0%
Cache is 39% full
当您首先比较不带共享类和带共享类的Tomcat占用空间的结果时,很难看到随着内存使用量的增加而运行共享类的好处。 但是,如果我们更深入地研究这些数字,情况就会开始改变:
- SHR从8400KB增加到14MB约6MB。 这是存储在共享类缓存中的数据的大小。
- 由于支持共享类(对象库等)所需的基础结构,RES从54MB略微增加到56MB。
- VIRT增加了,因为它是SHR和RES增加的组合值。
当我们启动第二个Tomcat实例并使用top
检查内存使用情况时,我们可以看到第二个实例(清单21中的过程24674)显示了相同数量的共享内存(报告为14MB SHR),但是减少了5MB。 RES大小(从56MB到51MB)以及虚拟内存的减少。 与Windows上的vadump一样, top
将正确地标识具有共享潜力的内存,但不会显示实际上挂接到共享内存中的其他进程。 在这种情况下,两个Tomcat实例都使用相同的共享类缓存,因此减少了它们的总体占用空间。 在此测试中,Tomcat服务器使用的缓存不到可用缓存的一半。 清单22显示了5911028字节的可共享ROMClass数据放置在缓存中(略小于6MB),这意味着通过共享缓存中的类,还有更多的空间来减少占用空间。
Tomcat性能:启动
启用共享类还可以减少Tomcat启动时间。 为了测量启动时间,我们使用写入<TOMCAT_HOME> / logs中的日志文件catalina.out中的计时。 为了提供进一步比较的基准,在未启用共享类的情况下启动了Tomcat。 清单23显示了报告的Tomcat启动时间(为清楚起见,省略了启动过程中写出的其他日志行):
清单23.禁用共享类的Tomcat启动时间
24-Apr-2008 13:01:08 org.apache.catalina.startup.Catalina
start INFO: Server startup in 1138 ms
然后将这些计时与启用了共享类的报告的计时进行比较,如清单24所示:
清单24.启用了AOT代码的共享类的Tomcat启动时间
24-Apr-2008 13:06:57 org.apache.catalina.startup.Catalina
start INFO: Server startup in 851 ms
如您所见,共享类将Tomcat的启动时间从1138ms减少到851ms,这意味着启动时间减少了25%。 这些改进来自类共享和AOT代码使用的混合。 要查看从AOT代码获得多少好处,可以使用-Xnoaot命令行选项禁用它,如清单25所示,并记下启动时间:
清单25.在没有AOT的情况下增加了启动时间
24-Apr-2008 13:03:50 org.apache.catalina.startup.Catalina
start INFO: Server startup in 950 ms
如您所见,清单25中所示的增加表明,将AOT代码存储在共享类缓存中的功能在减少Tomcat启动时间方面提供了宝贵的好处。
摘要
本文演示了共享类如何改善Java应用程序的启动时间并减少使用的内存量。 具体来说,我们向您展示了如何使用Tomcat和Eclipse量化共享类支持的占用资源和启动收益。 显然,并非所有应用程序的行为方式都相同,因此不会获得相同的收益。 但是,即使是我们使用的简单配置,也已显着减少了启动时间。
请记住,当多个应用程序在相同级别的IBM SDK上运行时,您将获得最大的收益,因为它们共享最多,收益最大。 但是,即使使用共享类缓存,即使是单个应用程序也会在启动时间上获得一些好处。
另外,我们还向您展示了如何通过Windows的vadump和Linux的top等工具对共享内存进行重复计数,以使您能够更准确地评估类共享所提供的内存节省。 尽管这些工具不能提供完美的内存使用情况视图,但我们已向您展示了如何在两行之间阅读。
翻译自: https://www.ibm.com/developerworks/java/library/j-sharedclasses/index.html