随着时间的推移,许多优化已经提高了 JVM 的性能。然而,尽管 Java 通常是第一个成功实现它们的虚拟机,但它们也经常被用于其他类似的平台。
1.即时编译
早期的 JVM 总是解释 Java 字节码。在普通应用程序中,Java 的性能损失比 C 大 10 到 20 倍。为了解决这个问题,Java 1.1 引入了即时 (JIT) 编译器。由于编译成本高,Java 1.2 引入了一个名为 HotSpot 的附加系统,并在 Java 1.3 中将其设为默认系统。使用这个框架,Java 虚拟机会不断分析程序性能,找出经常或重复执行的热点。然后针对这些热点进行优化,从而以最小的开销实现高性能执行,以降低性能要求不高的代码的开销。一些基准测试显示,通过这种方式,速度提高了 10 倍。但是,由于时间限制,编译器无法完全优化程序,因此生成的程序比本机代码替代方案慢。
2.自适应优化
自适应优化是计算机科学中的一种方法,它根据当前执行配置文件对程序的各个部分进行动态重新编译。通过简单的实现,自适应优化器可以简单地在即时编译和解释指令之间进行权衡。在另一个层面上,自适应优化可以利用本地数据条件来优化分支并使用内联扩展。
像 HotSpot 这样的 Java 虚拟机还可以对以前经过 JIT 的代码进行反优化。这允许执行积极(且可能不安全)的优化,同时仍然能够稍后对代码进行反优化并返回到安全路径。
3.垃圾收集
1.0 和 1.1 Java 虚拟机 (JVM) 使用标记清除收集器,这可能会在垃圾收集后对堆进行碎片化。从 Java 1.2 开始,JVM 改为使用分代收集器,其碎片整理行为要好得多。现代 JVM 使用各种方法,进一步提高了垃圾收集性能。
4.指针压缩
压缩的 Oops 允许 Java 5.0+ 使用 32 位引用寻址最多 32 GB 的堆。Java 不支持访问单个字节,仅支持默认为 8 字节对齐的对象。因此,堆引用的最低 3 位将始终为 0。通过将 32 位引用的分辨率降低到 8 字节块,可寻址空间可以增加到 32 GB。与使用 64 位引用相比,这显著减少了内存使用,因为 Java 使用引用的数量远远超过 C++ 等某些语言。Java 8 支持更大的对齐,例如 16 字节对齐,以支持使用 32 位引用最多为64 GB。
5.分割字节码验证
在执行类之前,Sun JVM 会验证其 Java 字节码。此验证是延迟执行的:仅在加载并准备使用特定类时才加载和验证类的字节码,而不是在程序开始时。但是,由于 Java 类库也是常规 Java 类,因此在使用时也必须加载它们,这意味着 Java 程序的启动时间通常比 C++ 程序等更长。
一种名为分时验证的方法,最早在 Java Platform, Micro Edition (J2ME) 中引入,自 Java 版本 6 以来在 JVM 中使用。它将 Java 字节码的验证分为两个阶段:
- 设计时 - 将类从源代码编译为字节码时
- 运行时 - 加载类时。
实际上,此方法通过捕获 Java 编译器对类流的了解并使用类流信息概要注释已编译的方法字节码来工作。这不会使运行时验证变得简单得多,但确实允许一些捷径。
6.逃逸分析和锁粗化
Java 能够在语言级别管理多线程。多线程允许程序同时执行多个进程,从而提高在具有多个处理器或核心的计算机系统上运行的程序的性能。此外,多线程应用程序即使在执行长时间运行的任务时也可以保持对输入的响应。
但是,使用多线程的程序需要特别注意线程之间共享的对象,当其中一个线程使用共享方法或块时,锁定对共享方法或块的访问。由于所涉及的底层操作系统级操作的性质,锁定块或对象是一项耗时的操作。
由于 Java 库不知道哪些方法将被多个线程使用,因此标准库在多线程环境中总是在需要时锁定块。
在 Java 6 之前,虚拟机总是在程序要求时锁定对象和块,即使不存在对象被两个不同的线程同时修改的风险。例如,在本例中,每次添加操作之前都会锁定一个本地 Vector,以确保它不会被其他线程修改(Vector 是同步的),但由于它严格地位于方法的本地,因此这是不必要的:
从 Java 6 开始,代码块和对象仅在需要时才被锁定,因此在上述情况下,虚拟机根本不会锁定 Vector 对象。
自版本 6u23 以来,Java 包含对逃逸分析的支持。
7.寄存器分配改进
在 Java 6 之前,客户端虚拟机中的寄存器分配非常原始(它们不能跨块存在),这在处理器寄存器较少的 CPU 设计中是一个问题,例如 x86。如果没有更多寄存器可用于操作,则编译器必须从寄存器复制到内存(或从内存复制到寄存器),这需要时间(寄存器的访问速度要快得多)。但是,服务器虚拟机使用了颜色图分配器,因此不存在此问题。
Sun 的 JDK 6 引入了寄存器分配的优化;然后可以跨块使用相同的寄存器(如果适用),从而减少对内存的访问。这导致一些基准测试中报告的性能提升了约 60%。
8.类数据共享
类数据共享(Sun 称之为 CDS)是一种减少 Java 应用程序启动时间并减少内存占用的机制。安装 JRE 时,安装程序会将系统 JAR 文件(包含所有 Java 类库的 JAR 文件,称为 rt.jar)中的一组类加载到私有内部表示中,并将该表示转储到称为“共享存档”的文件中。在后续 JVM 调用期间,此共享存档将进行内存映射,从而节省加载这些类的成本,并允许在多个 JVM 进程之间共享这些类的大部分 JVM 元数据。
对于小型程序,启动时间的相应改进更为明显。