Java技术,IBM风格:IBMDeveloperKit简介

针对 Java 平台 5.0 版本的 IBM Developer Kit 标志着显著的进步,它在语言特性和底层执行技术方面有重大改进。本文是一个分 5 部分的文章系列的第一篇,概述了 IBM 对它的虚拟机技术所做的一些主要改变和改进,包括传统的垃圾收集、共享类数据,以及在监视和调试工具及 API 方面的改进。但是,在讨论 IBM 实现的改进之前,我们先看看 Java 5.0 本身的改进。

Java 5.0 的改进

自从引入 Java 2 平台以来,Java 2 Standard Edition(J2SE 5.0)在 Java Class Library(JCL)API 和 Java 虚拟机(Java Virtual Machine,JVM)规范中引入了许多特性改进。这些特性在所有 Java 技术实现厂商的所有 5.0 实现中都可用了。它们主要涉及两个领域:开发的简化以及监视和管理。

简化开发的特性

5.0 版本中的简化开发特性的设计目的是,让开发人员能够用更少的代码建立简单构造,以及提供更多的编译时检查,从而帮助开发人员在开发周期中更早地发现问题。下面是对这些特性的简要介绍:

用泛型提供编译时类型安全性:泛型与 C++ 模板相似。一般的(即泛型(generic))类独立于具体的类型,在实例化时通过使用参数化类型(parameterized type)提供类型安全性。结合使用参数化类型和泛型类就可以进行编译时类型安全性检查,Java 5.0 平台中的集合类使用了这种方法。

扩展的 for 循环:这个新的语言构造与其他语言中的 for each 循环相似,它简化了循环遍历集合和数组的过程,因为不再需要使用显式定义的迭代器和索引变量。

原生类型的自动装箱:这个特性简化了将原生类型插入集合对象的过程,因为不再需要将 Java 原生类型(比如 int)装箱(box) 成对应的包装器类(比如 java.lang.Integer),在删除它们时也不需要开箱(unbox)。

类型安全的枚举:这个特性引入了 Java 语言对枚举类型的支持,提供了比使用静态 final 声明更强大且类型安全的解决方案。

支持导入常量:这个特性使静态方法和字段能够被导入,这样在访问静态成员时就不必使用完全限定的类名。

Java Language Metadata(标注):这个特性允许开发人员将标注(annotation) 添加到代码中。标注作为修饰符,可以添加到包、类、接口、方法或字段声明中。此信息存储在源代码文件和类文件中,工具和 Java 应用程序可以通过 Java Reflection API 获得它。用于文档编制、编译器检查和代码分析的工具可以使用这些额外信息。

并发工具:这个特性为开发并发类提供了基本构造块,包括线程池和线程安全的集合,并引入了低级锁定原语,包括信号量和原子性变量。

监视和管理特性

J2SE 新的监视和管理特性的设计目的是简化对 Java 运行时的状态的监视。可以使用监视和管理 API 从 Java 代码调用这些功能,或者使用 JVM Tools Interface(JVMTI)从 C 代码调用:

监视和管理 API:这个特性使 Java 程序或远程代理能够监视虚拟机的 “健康状态” 并观察其他系统级的活动和事件。可以利用这些特性开发自治和自适应系统。

JVM Tools Interface:JVMTI 是一种更轻量的、灵活的 JVM Profiling Interface(JVMPI)替代品,它是一个基于 C 的接口,用于编写开发时和运行时监视工具。

来自 IBM 的增值改进:概述

通过 Java 编译器、JCL API 和 JVM 规范在 5.0 中添加的规范和 API 改进影响了 Java 平台的所有新实现;另外,允许 Java 厂商在自己的 Java 实现中开发和提供自己的增值改进。IBM 以两种形式提供自己的改进:IBM 开发的 Java 语言扩展和 Java 运行时环境的 IBM 实现中的改进。

Java 语言扩展

IBM 的增值 Java 语言扩展包括三个主要组件:对象请求代理(object request broker,ORB)、XML 和 安全性。这三个组件是 IBM 提供的代码,提供了顾客和某些 IBM 产品需要的特性:

ORB:IBM 发起了 RMI-IIOP 的开发并将其包含到 J2SE 中,作为 RMI-JRMP 的替代品。在此之后,IBM 继续开发自己的实现,来满足顾客需求并确保版本之间的互操作性,尤其是对于 WebSphere Application Server。

XML 和 XSLT:IBM 基于 Apache Xerces Java 和 Xalan Java 开放源码项目(IBM 是这个项目的主要捐献者)开发了一个 XML/XLST 规范实现。IBM 包包含在 Java 5.0 规范中没有指定的 XML API,以及 Xerces Native Interface 和 XML Schema API。

安全性:IBM 通过标准 Java API 提供了广泛的安全服务。安全性组件包含各种安全性算法和机制的 IBM 实现。除了基本的安全性提供者之外,IBM 还为密码术硬件提供了 FIPS 支持。另外,提供了 iKeyman 实用程序来管理密钥和证书。

Java 运行时改进

Java 运行时的 IBM 实现针对 5.0 版进行了大量开发工作,影响到所有三个主要运行时组件:虚拟机(virtual machine,VM)、垃圾收集器(garbage collector,GC)和即时(just-in-time,JIT)编译器。这些努力有两个主要目的:提高应用程序的执行性能,以及提高可靠性、可用性和可伸缩性(RAS)。

实现这些改进的方式有两种:转移到所有 Java 运行时风格(Micro Edition(ME)、Standard Edition(SE)和 Enterprise Edition(EE))的通用代码基,IBM 为这些风格产生了端口;在这三个组件中分别引入改进。

在本文的其余部分和本系列的后续文章中,我们将详细讨论所有与 Java 运行时相关的改变。

通用代码基

IBM 长期以来一直为 Java 平台的所有三个版本开发实现。自 J2SE 5.0 发布以来,Java 运行时的 IBM 实现的所有底层组件都是在一个通用代码基上构建的。

通用代码基是使用一个框架引擎系统和可插入配置构造的,这可以支持最大程度的代码共享,同时可以迎合每个 Java 版本需要的任何功能差异。这改进了 J2SE IBM 实现的内存占用量、启动时间和性能,并改进了 J2ME IBM 实现的可伸缩性和可服务性。由于对相同的通用代码进行了广泛深入的测试,各个版本都由此受益了,因此改进了可靠性和稳定性。

Java 堆栈和线程寄存器中的值

Java 堆栈中的值是相对的,因为堆栈上的帧包含 Java 方法的局部变量。线程寄存器中的值也是相对的,因为这里存储当前正在执行的方法的局部变量和操作数。

垃圾收集器改进

除了利用可插入配置转移到通用的垃圾收集框架之外,对于 GC 组件还有 4 项主要改进:从保守性收集器转移到类型精确的收集器,引入了一个并行收集器,引入了分代的并发收集器,重新设计了详细 GC 日志记录设施。

类型精确的收集器

以前的 J2SE IBM 实现实现包含一个保守性(conservative) 垃圾收集器。这种收集器假设线程的 Java 堆栈或线程寄存器中的每个值都可能包含对 Java 对象的引用,因此称为保守性的。

这意味着垃圾收集器必须追踪每个值,并判断它是否指向 Java 堆上的对象,这可能导致将未被引用的对象标为被引用的 —— Java 堆栈或寄存器中的值可能实际上只是一个 long 值,但是这个值碰巧指向一个有对象存在的位置。在这种情况下,就会发生所谓的保留垃圾(retained garbage) 的情况,因为实际上已经不再需要的对象却在垃圾收集过程中保留下来了。这会使应用程序占用的内存比真正需要的内存量大。

将对象分配到碎片化的堆中

Java 对象要求创建一个单一的连续的内存区。如果 Java 堆有残留的碎片,那么可能没有足够的连续内存可以分配给给定对象,即使总内存是足够的。即使在内存看起来足够的情况下,这也会导致 OutOfMemoryError。

使用保守性收集器的另一个影响是需要对 Java 对象进行 pin 和 dose。当从 JNI(本机)代码引用对象时,它们就被 pin。当从 Java 堆栈或寄存器引用对象(有意或无意地成为保留的垃圾)时,对象就成为 dose 的。在垃圾收集器对 Java 对象进行紧凑排列期间,pin 和 dose 会阻止对象在 Java 堆上移动。这是因为在对象移动时,Java 堆栈和寄存器中的对象引用无法更新为对象的新位置;对于保留的垃圾,它实际上会变成一个不引用 Java 对象的值。

在垃圾收集的紧凑排列阶段,无法移动这些对象,这造成无法完全消除 Java 堆上对象的碎片化,因此留下了残留的碎片。这个问题造成在 Java 堆上即使看起来有足够的空闲内存,也无法分配对象,同时导致运行应用程序所需的 Java 堆比真正需要的大。

这两个问题都通过转移到类型精确的收集器(type-accurate collector) 解决了,这种收集器维护自己的经过良好描述的类型精确的堆栈。这避免了保留垃圾的问题,因为它知道一个值是否引用 Java 对象。因为能够编辑这些类型精确的堆栈,所以在紧凑排列期间对象可以移动了,因而消除了残留碎片问题。

并行收集器

紧凑排列是运行垃圾收集时最消耗时间的阶段。为了减少紧凑排列的时间,在 Java 5.0 技术的 IBM 实现中引入了并行紧凑排列(parallel compaction)。

并行紧凑排列允许多个线程帮助在 Java 堆上移动对象,从而将大量的小块空闲空间合并成少量的大块空间。实现的方法是,对于进程可以使用的每个 CPU,建立一个线程,并将 Java 堆分割成许多名义上的区域,这些线程分别负责每个区域的紧凑排列。

分代的并发收集器

分代并发(或称为 gencon)收集器利用了 “大多数对象在年轻时就死了” 这一并不可靠的假设,通过创建分成两代的 Java 堆来实现。通过将对象分成新老两代,收集过程可以主要关注年轻的对象。年轻(也称为婴儿(nursery)) 代使用一个半空间复制收集器,而老年(也称为长存的(tenured)) 代使用并发的标志扫描收集器。

消息 GC 日志记录更新

为了改进 GC 组件的 RAS 品质,GC 日志记录机制在两个方面进行了更新。日志记录器提供更详细的信息并采用 XML 格式而不是一般文本;由于第一次数据捕捉失败,所以它还将此数据的子集记录进内存中的缓冲区。

详细 GC 输出转移到基于 XML 的结构,因此更容易通过简单的 XML 读取器(比如 Web 浏览器)查看数据,也更容易通过各种详细 GC 分析工具进行分析。

内存中的跟踪缓冲区在发生失败时或者应用户的请求存储到文件,所以即使详细 GC 没有启用,它也可以提供关于 Java 内存使用情况和 GC 状态的基本信息,而且它大大提高了第一次失败数据捕捉信息的质量。

对 JIT 编译器的改进

尽管 Java 平台的用户对 JIT 编译器的内部不了解,但是它对 Java 运行时的应用程序执行性能影响很大。它在运行时将 Java 字节码转换为优化的机器码,从而大大提高了 Java 方法的运行速度。

从 1.4.2 到 5.0 版,许多改进和改变提高了 IBM JIT 编译器的性能,同时减小了 JIT 编译对正在运行的应用程序的影响。表 1 列出了主要的变化:

表 1. IBM JIT 编译器中的改进

1.4.2 5.0 在正在执行 Java 方法的 Java 线程上同步地编译 使用单独的异步编译线程 在需要时对方法进行编译 方法排队进行编译 对于 Java 方法,使用本机堆栈 维护一个单独的 Java 堆栈 只有一种编译优化 共有 5 个编译优化级别 方法只能编译一次 可以进行重新编译 可能出现包含循环的堆栈上方法替换 没有堆栈上替换 当应用 -Xdebug 时,禁用 JIT 在调试模式中,JIT 继续采用降低的优化级别进行编译

在这些改进中,主要的新特性是异步编译、多级优化和分析驱动的重新编译。

异步编译

Java 方法的 JIT 编译现在在一个独立于进行调用的线程的专用线程上异步地执行。这意味着,调用某个 Java 方法从而触发编译的线程在编译期间不再被阻塞。方法被添加到编译队列中,而线程可以继续运行并执行方法的非编译版本。方法编译之后,对方法的下一次调用会运行 JIT 编译的版本。为了确保频繁使用的方法优先编译,队列常常会重新调整次序并重新确定优先级。

在多处理器计算机上,异步编译可以大大提高启动时的性能,因为应用程序启动阶段的大部分是单线程的,而 Java 方法的编译由一个单独的处理器承担。

多级优化

现在,JIT 编译器能够采用 5 个优化级别之一对 Java 方法进行编译。通过使用单独的采样线程,编译器判断特定的 Java 方法花费多长时间,由此判断这个方法是否需要 JIT 编译,以及应该采用哪个优化级别进行编译。最常执行的 Java 方法采用最高的优化级别进行编译,这会获得最大的性能收益,但是完成编译所花费的时间成本也最大,内存需求也可能更高。不经常使用的方法采用较低的优化级别进行编译,这样编译完成得很快并产生显著的性能收益。

分析驱动的重新编译

如果由于应用程序运行方式的改变,Java 方法需要以更高的优化级别进行编译,那么 JIT 编译器能够对它们进行重新编译。在最高优化级别上,根据从正在执行的代码自动生成的动态分析数据执行重新编译。这些信息反映了方法本身实际上是如何被使用的。在收集短期数据之后,使用此数据对方法进行重新编译并相应地优化。

虚拟机改进

在 Java 5.0 技术的 IBM 实现中,与其他组件一样,虚拟机也得到了大量改进。两个最重要的改进是共享类的实现以及分析和调试方面的新特性。

共享类

共享类原来只在 z/OS 和 OS/390 的 Java 平台 IBM 实现中可用。共享类的这种实现已经被废弃,替换为所有平台上的新实现。

新的实现在共享内存中维护一个静态类数据缓存,它可以在实现新的共享类功能的所有 IBM Java 运行时之间共享;这个缓存在 Java 运行时的调用之间持续存在。共享类功能应用于所有 JCL 和基于类路径的类,而且通过使用简单的 API 很容易应用于定制类装载器装载的类。在缓存已经填充之后,这种功能可以减少内存占用和启动时间。对于在一台计算机上有多个 Java 运行时,或者运行时常常重新启动的情况,这个特性尤其有效。

分析和调试

除了支持 Java Debug Wire Protocol(JDWP)、JVM Profiling Interface(JVMPI)和 JVM Tools Interface(JVMTI)之外,IBM 调试器实现还有另外两个特性:高速调试(high-speed debug) 和热代码替换(hot-code replace)。

高速调试允许在运行 Java 调试器的同时执行 Java 方法的 JIT 编译,而且这种编译可以采用几乎完全的优化。这在调试大型 Java 应用程序时很有用,例如那些在 J2EE 堆栈上运行的应用程序;在这些应用程序中如果禁用 JIT,那么调试时的性能会很差。

热代码替换允许在调试器下动态地修改源代码,并立即运行新代码而不需要重新启动应用程序。

能够在调试器下几乎全速地运行代码并动态地修改代码,这大大提高了应用程序开发的效率。

可靠性、可用性和可服务性改进

对于对 Java 应用程序和 Java 运行时本身进行监视和故障调试,Java 平台的 IBM 实现已经提供了基础设施和工具。Java 运行时中已经添加了许多这方面的改进,包括写入内部缓冲区的连续低级跟踪(作为第一次失败数据捕捉的辅助措施)、一个用于监视本机内存使用情况的新选项和一个强大的 JNI 代码检验器,并对转储和跟踪引擎进行了重新设计。5.0 的实现还添加了一个基于 Java 的工具 API,用于查询系统转储文件,这使工具开发人员能够访问转储中关于对象、线程、锁等的信息,而不需要掌握 JVM 的内部结构。

跟踪引擎

跟踪引擎已经重新改造过了,并添加了一个内部 “flight recorder”。它连续地将关键的 VM 和 JCL 跟踪点写入每个线程专用的回绕型缓冲区。GC 数据也写入一个单独的回绕型缓冲区,从而确保可以很容易地获得此数据,并提供一定的 GC 历史数据。

除了 VM 的内部跟踪之外,方法跟踪功能能够跟踪 Java 代码(包括 JCL 提供的代码和应用程序代码)的进入方法和退出方法事件。这不需要对应用程序代码做任何修改,它会提供时间戳、线程 ID 和参数信息。

转储引擎

通过对转储引擎的重构,可以触发转储的事件数量从 3 个增加到了 14 个。现在,可以在发生许多事件时生成转储,比如停止 VM、装载和卸载类、启动和停止线程、GC 周期以及抛出/捕获/未捕获异常。这些新功能与触发事件的能力相结合,就能够非常灵活地控制 Java 运行时何时创建转储以及创建什么类型的转储。

DTFJ Tooling API

Dump Toolkit and Framework for Java(DTFJ)是一个基于 Java 的 API,用来访问 Java 进程的系统转储中的事后信息。这使工具开发人员能够访问转储中关于系统、进程、Java VM 和 Java 应用程序的信息,而不需要了解相关结构在内存中是如何布局的。这样就能够编写更好的事后分析工具。

结束语

IBM Developer Kit for Java 5.0 引入了大量特性和功能。其中一些改进(包括性能和可靠性改进)会在从以前版本迁移到 5.0 时透明地提供给用户,其他改进需要调用。在本系列以后的几期中,我们将深入讨论 IBM 提供的一些增值改进的技术,包括垃圾收集策略、共享类和调试特性,以及如何利用它们。

人生就是要感受美丽的、善良的,丑恶的、病态的。

Java技术,IBM风格:IBMDeveloperKit简介

相关文章:

你感兴趣的文章:

标签云: