Java™ 5.0 平台的 IBM 实现中新的共享类特性提供了一种完全透明和动态的方法,可以共享已经装载的所有类,而不会对共享类数据的 JVM 施加限制。这个特性为减少虚拟内存占用和改进启动时间提供了一个简单且灵活的解决方案,大多数应用程序都能够因此受益。本文讨论这个特性是如何工作的、如何使用它以及何时使用它,还介绍它提供的一些特性。
在 Java 虚拟机(JVM)进程之间共享已经装载的类,这种概念并不是新的。例如,Sun 的 CDS 特性将系统类写到一个只读文件中,这个文件在内存中映射到 JVM。IBM z/OS® 1.4.2 JVM 中的 Shiraz 特性使用一个主 JVM 填充类缓存,然后从 JVM 可以共享这个类缓存。
JVM 5.0 的 IBM 实现进一步发展了这个概念,允许将所有 系统类和应用程序类存储在共享内存中一个一致的动态类缓存中。在支持 JVM 的 IBM 实现的所有平台上都支持这个共享类 特性。这个特性甚至支持与运行时字节码修改进行集成,这将在本文 后面 讨论。
共享类特性是从头设计的,它是一个可以打开和关闭的选项,可以减少虚拟内存占用并改进 JVM 启动时间。因此,它非常适合多个 JVM 运行相似代码的环境或者 JVM 常常重新启动的环境。
除了 JVM 及其类装载器中的运行时类共享支持之外,还有一个公共的 Helper API,可以将类共享支持集成到定制的类装载器中,本文将 详细 讨论这个问题。
它如何工作
我们先看看共享类特性如何操作的技术细节。
启用类共享
启用类共享的方法是将 -Xshareclasses[:name=<cachename>]
添加到现有的 Java 命令行上。当 JVM 启动时,它寻找给定名称的类缓存(如果没有提供名称,那么选择一个默认名称),并按照需要连接现有的缓存或创建一个新的缓存
使用参数 -Xscmx<size>[k|m|g]
指定缓存的大小;这个参数只应用于 JVM 创建新缓存的情况。如果省略这个选项,那么选择一个与平台相关的默认值(通常是 16MB)。注意,一些操作系统设置可能会限制可分配的共享内存量,例如 Linux 上的 SHMMAX
通常设置为大约 20MB。这些设置的细节可以在适当的用户指南的 Shared Classes 部分中找到(参见 参考资料 中的链接)。
类缓存
类缓存 是一个大小固定的共享内存区,它在使用它的 JVM 的生命周期之外仍然持久地存在。一个系统上可以有任意数量的共享类缓存,这只受操作系统设置的限制;但是一个 JVM 在它的生命周期中只能连接一个缓存。
JVM 并不拥有缓存,也没有主/从 JVM 的概念;实际上,任意数量的 JVM 都可以并行地读写缓存。在两种情况下会删除缓存:使用 JVM 实用程序显式地销毁它,或者操作系统重新启动时(缓存无法在操作系统重新启动时持久存在)。缓存的大小无法增长,当它被填满时,JVM 仍然可以从其中装载类,但是不能再向其中添加任何类。有许多用来管理活动缓存的 JVM 实用程序,后面的 “共享类实用程序” 一节将讨论这些程序。
如何对类进行缓存?
当 JVM 装载一个类时,它先查看需要的类是否已经在缓存中存在。如果是这样,那么它从缓存装载这个类。否则,它从文件系统装载这个类并将其写到缓存中(在 defineClass()
调用中进行这一操作)。因此,不进行共享的 JVM 采用以下类装载器查找次序:
- 类装载器缓存
- 父类装载器
- 文件系统
与其相反,进行共享的 JVM 采用以下次序:
- 类装载器缓存
- 父类装载器
- 共享缓存
- 文件系统
使用公共的 Helper API 在缓存中读写类,这个 API 已经集成到了 java.net.URLClassLoader
的 IBM 实现中。因此,任何扩展 java.net.URLClassLoader
的类装载器都会自动地获得类共享支持。
类的哪些部分被缓存?
在 JVM 的 IBM 实现中,Java 类分成两个部分:一个称为 ROMClass
的只读部分,其中包含不可变的所有类数据;一个 RAMClass
部分,其中包含可变的数据,比如静态类变量。RAMClass
指向 ROMClass
中的数据,但是这两部分是完全分开的,这意味着 ROMClass
可以十分安全地在 JVM 之间共享,甚至在同一个 JVM 中的 RAMClass
之间共享。
在不进行共享的情况下,当 JVM 装载一个类时,它单独创建 ROMClass
和 RAMClass
并将它们存储在自己的本地进程内存中。在进行共享的情况下,如果 JVM 在类缓存中发现了 ROMClass
,那么它只需要在自己的本地内存中创建 RAMClass
;RAMClass
引用共享的 ROMClass
。
因为大部分类数据存储在 ROMClass
中,因此可以节省虚拟内存。(“虚拟内存占用” 一节将详细讨论这个问题。)已经填充的缓存还会显著改进 JVM 启动时间,因为每个缓存的类的一部分定义工作已经完成了,而且会从内存装载类而不是从文件系统装载。填充新缓存导致的启动时间开销(在本文后面 讨论)并不显著,因为每个类只需在定义时重新定位到缓存中。
如果文件系统中的类发生变化,那么会怎么样?
因为缓存是无限期持久存在的,文件系统更新可能会使缓存中的类失效。因此,当类装载器请求一个共享的类时,缓存代码要负责确保返回的类总是与从文件系统装载的类完全一样。当装载类时这一检查会透明地进行,所以用户可以在共享类缓存的生命周期内修改和更新任意类,系统总会装载正确的类。
![](https://i-blog.csdnimg.cn/blog_migrate/c6ccbc96c20bebb0d73e8e6368158ef5.gif) |
类修改的陷阱:两个示例 假设 JVM 将一个类 C1 装载到缓存中。然后,当 JVM 关闭时,C1 被修改并重新编译。当 JVM 重新启动时,它肯定不应该装载 C1 的缓存版本。 同样,假设 JVM 的类路径是 /mystuff:/mystuff/myClasses.jar 。它从 myClasses.jar 将 C2 装载到缓存中。然后,将一个不同的 C2.class 添加到 /myStuff 中,启动另一个 JVM 来运行同一个应用程序。JVM 也不应该装载 C2 的缓存版本。 | |
JVM 探测文件系统更新的方法是,将时间戳值存储在缓存中并在装载每个类时比较缓存的值和实际值。如果它探测到一个 JAR 文件已经更新了,那么它不知道哪些类已经更新了,所以缓存中所有从这个 JAR 装载的类都立即被标为过时的,不能再从缓存中装载它们。当从文件系统装载这个 JAR 中的类并重新添加到缓存中时,只添加实际修改过的类;那些没有修改的类实际上没有过时。
无法从缓存中清除类,但是 JVM 会尽可能高效地利用空间。例如,同样的类不会添加两次,即使它是从许多不同的位置装载的。所以,如果三个不同的 JVM 分别从 /A.jar、/B.jar 和 /C.jar 装载同样的类 C3
,类数据仍然只添加一次,但是有三段元数据描述装载这个类的三个位置。
共享类实用程序
有许多实用程序可以用来管理活动缓存,它们都是 -Xshareclasses
的子选项。(输入 java -Xshareclasses:help
就可以看到 -Xshareclasses
的所有有效子选项。)
注意,实用程序(除了 expire
之外)实际上不启动 JVM,它们执行所需的操作,然后退出,并不运行类。还要注意,每个实用程序都导致 Java 启动程序输出消息 Could not create the Java virtual machine
,因为没有启动 JVM。这不是错误。
为了演示这些选项的使用方法,我们来看一些示例。首先,用不同的缓存名称运行 HelloWorld
类,从而创建两个缓存,见清单 1:
清单 1. 创建两个缓存
C:/j9vmwi3223/sdk/jre/bin>java -cp . -Xshareclasses:name=cache1 Hello Hello C:/j9vmwi3223/sdk/jre/bin>java -cp . -Xshareclasses:name=cache2 Hello Hello
|
运行 listAllCaches
子选项列出系统上的所有缓存并指出是否正在使用它们,见清单 2:
清单 2. 列出所有缓存
C:/j9vmwi3223/sdk/jre/bin>java -Xshareclasses:listAllCaches Shared Cache Last detach time cache1 Sat Apr 15 18:47:46 2006 cache2 Sat Apr 15 18:51:15 2006 Could not create the Java virtual machine.
|
运行 printStats
选项输出指定缓存的统计信息,见清单 3。这里显示的字段的意义请参考用户指南(参见 参考资料 中的链接)。
清单 3. 缓存的统计信息
C:/j9vmwi3223/sdk/jre/bin>java -Xshareclasses:name=cache1,printStats Current statistics for cache "cache1": base address = 0x41D10058 end address = 0x42D0FFF8 allocation pointer = 0x41E3B948 cache size = 16777128 free bytes = 15536080 ROMClass bytes = 1226992 Metadata bytes = 14056 Metadata % used = 1% # ROMClasses = 313 # Classpaths = 2 # URLs = 0 # Tokens = 0 # Stale classes = 0 % Stale classes = 0% Cache is 7% full Could not create the Java virtual machine.
|
对指定的缓存运行 printAllStats
选项列出这个缓存的全部内容以及 printStats
统计信息。会列出缓存中存储的每个类以及上下文数据,比如类路径数据。在清单 4 中,可以看到列出了 JVM 引导类路径,后面是一些类以及装载它们的位置:
清单 4. 列出一个缓存的全部内容
C:/j9vmwi3223/sdk/jre/bin>java -Xshareclasses:name=cache1,printAllStats Current statistics for cache "cache1": 1: 0x42D0FAB0 CLASSPATH C:/j9vmwi3223/sdk/jre/lib/vm.jar C:/j9vmwi3223/sdk/jre/lib/core.jar C:/j9vmwi3223/sdk/jre/lib/charsets.jar C:/j9vmwi3223/sdk/jre/lib/graphics.jar C:/j9vmwi3223/sdk/jre/lib/security.jar C:/j9vmwi3223/sdk/jre/lib/ibmpkcs.jar C:/j9vmwi3223/sdk/jre/lib/ibmorb.jar C:/j9vmwi3223/sdk/jre/lib/ibmcfw.jar C:/j9vmwi3223/sdk/jre/lib/ibmorbapi.jar C:/j9vmwi3223/sdk/jre/lib/ibmjcefw.jar C:/j9vmwi3223/sdk/jre/lib/ibmjgssprovider.jar C:/j9vmwi3223/sdk/jre/lib/ibmjsseprovider2.jar C:/j9vmwi3223/sdk/jre/lib/ibmjaaslm.jar C:/j9vmwi3223/sdk/jre/lib/ibmjaasactivelm.jar C:/j9vmwi3223/sdk/jre/lib/ibmcertpathprovider.jar C:/j9vmwi3223/sdk/jre/lib/server.jar C:/j9vmwi3223/sdk/jre/lib/xml.jar 1: 0x42D0FA78 ROMCLASS: java/lang/Object at 0x41D10058. Index 0 in classpath 0x42D0FAB0 1: 0x42D0FA50 ROMCLASS: java/lang/J9VMInternals at 0x41D106E0. Index 0 in classpath 0x42D0FAB0 1: 0x42D0FA28 ROMCLASS: java/lang/Class at 0x41D120A8. Index 0 in classpath 0x42D0FAB0 ...
|
使用 destroy
选项销毁指定的缓存,见清单 5。使用 destroyAll
销毁所有未使用的缓存和用户有权销毁的缓存。
清单 5. 销毁缓存
C:/j9vmwi3223/sdk/jre/bin>java -Xshareclasses:name=cache1,destroy JVMSHRC010I Shared Cache "cache1" is destroyed Could not create the Java virtual machine. C:/j9vmwi3223/sdk/jre/bin>java -Xshareclasses:listAllCaches Shared Cache Last detach time cache2 Sat Apr 15 18:51:15 2006 Could not create the Java virtual machine.
|
expire
选项(见清单 6)是一个可以添加到命令行的清理选项,它的作用是如果在指定的时间内(分钟数)没有 JVM 连接这个缓存,那么就自动销毁缓存。这是惟一一个不会导致 JVM 退出的实用程序。清单 6 寻找一周(大约 10000 分钟)内没有使用过的缓存,并在启动 VM 之前销毁它们:
清单 6. 销毁一周内没有使用过的缓存
详细选项
详细选项提供关于类共享正在做什么的有用反馈。它们都是 -Xshareclasses
的子选项。本节给出一些示例,演示如何使用详细输出。
verbose
选项(见清单 7)给出关于 JVM 启动和关闭的简明状态信息:
清单 7. 获得 JVM 状态信息
C:/j9vmwi3223/sdk/jre/bin>java -cp . -Xshareclasses:name=cache1,verbose Hello [-Xshareclasses verbose output enabled] JVMSHRC158I Successfully created shared class cache "cache1" JVMSHRC166I Attached to cache "cache1", size=16777176 bytes Hello JVMSHRC168I Total shared class bytes read=0. Total bytes stored=1176392
|
verboseIO
选项将每个类装载请求的一个状态行输出到缓存。为了理解 verboseIO
输出,应该了解类装载器的层次结构,这样才能看出哪些类是由非引导类装载器装载的。在寻找类时,每个类装载器必须沿着层次结构向上委托请求,直到到达引导装载器。在输出中,每个类装载器被赋予一个惟一的 ID,引导装载器的 ID 总是 0。
注意,有时候即使类已经被缓存了,verboseIO
仍然显示从磁盘装载类并存储到缓存中,这是正常的。例如,从应用程序类路径中的每个 JAR 装载的第一个类总是从磁盘装载并存储,无论它是否在缓存中存在。
在 清单 8 中,第一部分演示缓存的填充,第二部分演示缓存类的读取:
verboseHelper
子选项(见 清单 9)是一个高级选项,提供来自 Helper API 的状态输出。设计它是为了帮助使用 Helper API 的开发人员了解 Helper API 的操作方式。关于此输出的更多细节见 JVM 诊断指南(参见 参考资料 中的链接)。
运行时字节码修改
运行时字节码修改正在成为将功能加入 Java 类的流行方法。可以使用 JVM Tools Interface(JVMTI)钩子执行运行时字节码修改(参见 参考资料 中的链接);另外,类装载器还可以在定义类之前替换类字节码。这给类共享带来了额外的挑战,因为一个 JVM 可能缓存经过修改的字节码,共享同一个缓存的另一个 JVM 不应该装载它们。
但是,由于 IBM 共享类实现的动态性质,使用不同修改方式的多个 JVM 可以安全地共享同一个缓存。实际上,如果字节码修改的开销很大,那么对经过修改的类进行缓存有很大的好处,因为变换只需要执行一次。惟一的要求是字节码修改应该是确定性的且可预测的。修改并缓存一个类之后,就不能再修改它。
可以使用 -Xshareclasses
的 modified=<context>
子选项共享经过修改的字节码。上下文是一个用户定义的名称,它在缓存中创建一个分区,这个 JVM 装载的所有类都存储在这个分区中。要使用这个修改版字节码的所有 JVM 都应该指定这个上下文名称,这样就会从这个缓存分区装载类。如果使用同一个缓存的 JVM 没有指定 modified
子选项,就会按一般方式寻找并存储类。
潜在的陷阱
如果 JVM 注册了 JVMTI 代理来修改类字节码,而且没有 使用 modified
子选项,那么仍然可以安全地管理其他一般 JVM 或使用其他代理的 JVM 之间的类共享,但是由于要进行额外的检查,会有一点儿性能开销。因此,使用 modified
子选项总会提高效率。
注意,只有在 JVMTI 代理存在的情况下,JVM 才能意识到将进行字节码修改。因此,如果定制的类装载器在定义类之前修改类字节码,但是没有使用 JVMTI 和 modified
子选项,那么就假设定义的类是未修改的,其他 JVM 就无法正确地装载它们。
关于共享经过修改的字节码的更详细信息,请参考 JVM 诊断指南(参见 参考资料)。
使用 Helper API
IBM 提供了共享类 Helper API,使开发人员能够将类共享支持集成到定制的类装载器中。只有未扩展 java.net.URLClassLoader
的类装载器才需要 Helper API,因为扩展 java.net.URLClassLoader
的类装载器会自动继承类共享支持。
对 Helper API 的全面介绍超出了本文的范围,但是这里给出一个一般性概述。如果想详细了解 Helper API,在 下载 一节中可以找到完整的 Javadoc,诊断指南(参考资料)也提供了更多信息。
![](https://i-blog.csdnimg.cn/blog_migrate/c6ccbc96c20bebb0d73e8e6368158ef5.gif) |
OSGi 支持 定制类装载器框架的一个好例子是 Eclipse 3.x 中使用的 OSGi。除非将类共享支持添加到 OSGi 插件类装载器中,否则共享的类只包括引导类以及启动 Eclipse 和 OSGi 框架所需的应用程序类。不能共享插件代码,因为它们是由定制的 OSGi 插件类装载器装载的,而 OSGi 插件类装载器没有类共享支持。注意,对于这种情况,IBM 提供了一个 OSGi Adaptor 插件,可以在插件类装载器中启用类共享支持。这个插件在 com.ibm.cds_1.0.0.zip 文件中,可以从 下载 一节获得。 | |
Helper API:概述
所有 Helper API 类都在 com.ibm.oti.shared
包中,并包含在 jre/lib 目录的 vm.jar 中。希望共享类的每个类装载器都必须从 SharedClassHelperFactory
获得一个 SharedClassHelper
对象。创建 SharedClassHelper
之后,它属于请求它的类装载器,只能存储这个类装载器定义的类。SharedClassHelper
为类装载器提供一个简单的 API,可以在 JVM 连接的类缓存中寻找和存储类。如果这个类装载器被垃圾收集,那么它的 SharedClassHelper
也被垃圾收集。
使用 SharedClassHelperFactory
SharedClassHelperFactory
是一个单实例对象,可以使用静态方法 com.ibm.oti.shared.Shared.getSharedClassHelperFactory()
获得它;如果 JVM 中启用了类共享支持,那么这个方法返回一个工厂,否则返回 null
。
使用 SharedClassHelper
这个工厂可以返回三种不同的 SharedClassHelper
,分别供不同类型的类装载器使用:
SharedClassURLClasspathHelper
:这个 helper 供那些有 URL 类路径概念的类装载器使用。使用 URL 类路径数组在缓存中存储和寻找类。必须可以通过类路径中的 URL 资源访问文件系统上要缓存的类。这个 helper 还对在它的生命周期内如何修改类路径有一些限制。
SharedClassURLHelper
:这个 helper 供那些没有类路径概念,可以从任意 URL 装载类的类装载器使用。必须可以通过 URL 资源访问文件系统上要缓存的类。
SharedClassTokenHelper
:这个 helper 实际上将共享类缓存转换为一个简单的哈西表 —— 类按照一个对于缓存无意义的字符串键标志 进行存储。这是惟一一种不提供动态更新功能的 helper,因为存储的类没有相关联的文件系统上下文。
每个 SharedClassHelper
都有两个基本方法,但是在 helper 类型之间方法的参数有差异:
- 在类装载器向它的父类装载器请求类(如果存在的话)之后,应该调用
byte[] findSharedClass(String classname...)
。如果 findSharedClass()
没有返回 null
,那么类装载器应该在返回的字节数组上调用 defineClass()
。注意,这个函数为 defineClass()
返回一个特殊的 cookie,而不是实际的类字节,所以不能修改字节。
- 在定义一个类之后,应该立即调用
boolean storeSharedClass(Class clazz...)
。如果成功地存储了这个类,那么这个方法返回 true
,否则返回 false
。
其他考虑因素
在应用程序中部署类共享时,需要考虑到安全性和缓存调整等因素。这里简短地讨论这些考虑因素。
安全性
在默认情况下,以用户级安全性创建类缓存,所以只有创建这个缓存的用户才能访问它。因此,每个用户的默认缓存名称是不同的,从而避免了冲突。在 UNIX 上,有一个指定 groupAccess
的子选项,它允许创建这个缓存的用户的主组中的所有用户访问这个缓存。但是,不管使用什么访问级别,只有创建缓存的用户或根用户能够销毁缓存。
除此之外,如果安装了 SecurityManager
,那么类装载器只能在被显式地授予正确权限的情况下共享类。关于设置这些权限的更多细节,请参考用户指南(参见 参考资料)。
垃圾收集和即时编译
启用类共享并不影响类的垃圾收集(GC)。仍然按照非共享情况下的方式对类和类装载器进行垃圾收集。另外,在使用类共享时,对 GC 模式或配置没有限制。
不能将即时(JIT)编译的代码存储在类缓存中,所以在启用类共享时 JIT 的行为没有变化。
缓存大小限制
当前缓存大小的理论最大值是 2GB。缓存大小受到以下因素的限制:
- 可用磁盘空间(只适用于 Microsoft Windows)。 在 javasharedresources 目录中创建一个内存映射文件来存储类数据。这个目录在用户的 %APPDATA% 目录中创建。每当重新启动 Windows 时,删除共享缓存文件。
- 可用系统内存(只适用于 UNIX)。 在 UNIX 上,缓存放在共享内存中,JVM 将一个配置文件写到 /tmp/javasharedresouces,从而让所有 JVM 能够根据名称找到共享内存区域。
- 可用虚拟地址空间。 因为进程的虚拟地址空间在共享类缓存和 Java 堆之间分享,所以增加 Java 堆的大小会减小可以创建的共享类缓存。
一个示例
为了实际演示类共享的好处,本节提供一个简单的图形演示程序。这个程序的源代码和二进制代码可以从 下载 一节获得。
这个演示程序搜索 jre/lib 目录并打开每个 JAR,在找到的每个类上调用 class.forName()
。这会将大约 12,000 个类装载到 JVM 中。演示程序报告 JVM 花了多长时间装载这些类。显然,这是个有点儿不自然的示例,因为这个测试只进行类装载,但是它确实展现了类共享的好处。我们来运行这个应用程序并看看结果。
类装载的性能
- 从 下载 一节下载 shcdemo.jar。
- 使用清单 10 中的命令,在不启用类共享的情况下两次运行这个测试,从而对系统磁盘缓存进行 “预热”:
清单 10. 对磁盘缓存进行预热
C:/j9vmwi3223/sdk/jre/bin>java -cp C:/shcdemo.jar ClassLoadStress
|
当出现图 1 所示的窗口时,按下按钮。应用程序将装载类。
图 1. 按下按钮
装载类之后,应用程序报告它装载了多少个类以及花费的时间,见图 2:
图 2. 结果
您会注意到,应用程序每次运行时都可能会快一点儿;这是由于操作系统优化造成的。
- 现在在启用类共享的情况下运行这个演示程序,见 清单 11。会创建一个新的缓存,所以这次运行显示了填充新缓存所花费的时间。应该将缓存的大小指定为大约 50MB,从而确保能够存储所有的类。清单 11 给出命令行和一些输出示例。
如图 3 所示,这次运行花费的时间应该比前几次略微长一点儿,因为演示程序要填充共享类缓存。还可以使用 printStats
(见清单 12)来查看共享类缓存中存储的类的数量:
图 3. 冷缓存的结果
清单 12. 查看缓存类的数量
C:/j9vmwi3223/sdk/jre/bin>java -Xshareclasses:name=demo,printStats Current statistics for cache "demo": base address = 0x41D10058 end address = 0x44F0FFF8 allocation pointer = 0x44884030 cache size = 52428712 free bytes = 6373120 ROMClass bytes = 45563864 Metadata bytes = 491728 Metadata % used = 1% # ROMClasses = 12212 # Classpaths = 3 # URLs = 0 # Tokens = 0 # Stale classes = 0 % Stale classes = 0% Cache is 87% full Could not create the Java virtual machine.
|
- 现在,用同样的 Java 命令行再次启动这个演示程序。这一次,它应该会从共享类缓存中读取类,见 清单 13。
可以清楚地看到类装载时间方面的显著改进。同样,由于操作系统优化,每次运行这个演示程序时都应该会看到性能略微提高。运行这个测试所用的是一个单处理器的 1.6 GHz x86 兼容笔记本,运行的操作系统是 Windows XP:
图 4. 热缓存的结果
还可以测试许多不同的情况。例如,可以使用 javaw
命令并启动多个演示程序,让它们一起装载所有的类,从而观察并行性能。
在真实的场景中,通过使用类共享获得的 JVM 启动时间收益取决于应用程序装载的类数量:HelloWorld 这样的简单程序体现不出很大的收益,但是大型 Web 服务器得到的收益肯定很大。无论如何,这个示例说明对类共享进行实验是非常容易的,所以您可以轻松地测试受益的程度。
虚拟内存占用
在多个 JVM 中运行示例程序时,很容易看出虚拟内存的节省。
下面两张 Task Manager 屏幕图是使用与前例相同的机器获得的。在图 5 中,运行了演示程序的 5 个实例,没有启用类共享。在图 6 中,使用与前面一样的命令行运行了 5 个实例,启用了类共享:
图 5. 没有启用类共享的 5 个演示程序
图 6. 启用了类共享的 5 个演示程序
可以清楚地看到,在启用类共享的情况下提交开销相当低。Windows 将 VM 大小加在一起计算出提交开销。因为共享的缓存类数据的总量大约是 45MB,可以看到每个 JVM 的内存使用量大约是 VM 大小加上缓存的类数据量。
这两个示例开始之前的提交开销是大约 295MB。这意味着第一个示例使用 422MB,第二个示例使用 244 MB,节省了 178MB。
结束语
Java 5.0 平台的 IBM 实现中新的共享类特性为减少虚拟内存占用和改进 JVM 启动时间提供了一个简单且灵活的方法。在本文中,您看到了如何启用这个特性、如何使用缓存实用程序以及如何对收益进行度量。
本系列的最后一篇文章将介绍 Java 平台的 IBM 实现中一些新的调试、监视和分析工具。还将讲解如何使用它们快速地分析和调试 Java 应用程序。