在 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 的类装载器都会自动地获得类共享支持。