4.3 Java和JIT编译器版本

各种测试的编译器之间是有差别的,我们来看下如何获得合适的编译器。在你下载 Java 时,需要选择版本,而最终的选择取决于你所用的平台。Java 版本的选择也会影响 JIT 编译器。到目前为止,我们讨论了 client 和 server 编译器,实际上 JIT 编译器有 3 种版本:

  • 32 位 client 编译器(-client

  • 32 位 server 编译器(-server

  • 64 位 server 编译器(-d64

从某种程度上说,你选择使用的编译器取决于所给的命令行选项参数(-server 等)。然而事情并没有这么简单。

32 位还是 64 位?

如果是 32 位操作系统,那你必须使用 32 位的 JVM。如果是 64 位操作系统,那你可以选择 32 位或 64 位 Java。并没有规定 64 位操作系统必须使用 64 位 Java。

如果堆小于 3 GB,32 位的 Java 会更快一些,并且内存占用也更少。这是因为 JVM 内部的指针只有 32 位,操作 32 位指针的代价要少于 64 位指针的(即便你使用的是 64 位 CPU)。而且 32 位指针所占的内存也少。

第 8 章讨论了压缩的普通对象指针(ordinary object pointers,oops),这是一种在 64 位 JVM 中使用 32 位寻址的方法。不过,即便有这种优化,64 位 JVM 的内存占用仍然大于 32 位 JVM,这是因为它所用的本地代码依然是 64 位寻址。

32 位 JVM 的不足之处是进程最多只能占用 4 GB 内存(某些版本的 Windows 为 3 GB,某些老版本的 Linux 为 3.5 GB)。而且还包括了堆、永久代(permgen)和本地代码以及 JVM 所用的本地内存。还有一个非常特殊的案例,因为 32 位 JVM 无法使用 CPU 的 64 位寄存器,所以大量使用 longdouble 变量的程序在 32 位 JVM 上就会比较慢。

在 32 位 JVM 上运行的程序,只要与 32 位寻址空间吻合,无论机器是 32 位还是 64 位,都要比在类似配置的 64 位 JVM 上运行时快 5% 到 20%。比如,本章前面讨论的股票批处理程序,在我台式机的 32 位 JVM 上运行时,就要快 20%。

在下载特定操作系统的 Java 时,只有两个选项:32 位或 64 位 JVM。而准确地说,32 位 JVM(最多)有两种编译器,而 64 位只有一种编译器。(实际上 64 位 JVM 也有两种编译器,因为分层编译需要有 client 编译器的支持。但在只有 client 编译器时,64 位 JVM 无法运行。)

不过一旦安装之后,事情就变得有些复杂了。在大多数平台上,32 位和 64 位是分开安装的。在你的电脑里可以有两种 JVM,但必须通过不同路径来区分引用。因此,在我测试 Linux 的机器上,我安装了 /export/VMs/jdk1.7.0-32bit 和 /export/VMs/jdk1.7.0-64bit,通过设置相应的 PATH 来选择。

在 Solaris 上则有所不同:64 位的安装会覆盖 32 位。因此所有的 3 种编译器都在相同的路径上。对于最终用户来说更容易。尤其是,Java 安装在系统路径 /usr/bin 上时,用户就总能通过命令行从 3 种编译器中指定他所想要的。不过这种安装方式也有问题,由于 HotSpot 的开发人员通常使用 Solaris 作为主要的开发系统,所以事情就变得更复杂了,所用的安装方式会使讨论(有时是文档)变得令人迷惑。

关于编译器,最后要说的一点是:考虑到兼容性,指定使用哪种编译器的规则并没有被严格遵守。如果 64 位 JVM 上设定 -client,那无论怎样应用都会使用 64 位 server 编译器。如果 32 位 JVM 上设定 -d64,就会抛出所给实例不支持 64 位 JVM 的错误。

总结一下:编译器的选择取决于安装的 JVM 是 32 位还是 64 位,以及传递给 JVM 的编译器参数。表 4-4 显示了在给定 JVM 安装版本、给定参数时的编译器。

表4-4:不同JVM安装版本和不同参数下的编译器

安装的JVM版本 -client -server -d64
Linux 32 位 32 位 client 编译器 32 位 server 编译器 出错
Linux 64 位 64 位 server 编译器6 4 位 server 编译器 64 位 server 编译器
Mac OS X 64 位 server 编译器 64 位 server 编译器 64 位 server 编译器
Solaris 32 位 32 位 client 编译器 32 位 server 编译器 出错
Solaris 64 位 32 位 client 编译器 32 位 server 编译器 64 位 server 编译器
Windows 32 位 32 位 client 编译器 32 位 server 编译器 出错
Windows 64 位 64 位 server 编译器 64 位 server 编译器 64 位 server 编译器

在 Java 8 中,上述所有情况的默认值是 server 编译器,同时也默认开启分层编译。

不设定编译器参数会发生什么?在代码运行时,JVM 选择默认的编译器:默认编译器是运行时选择。这个选择基于 JVM 认为机器是“client”机器还是“server”机器。这个决策综合考虑了操作系统以及机器上的 CPU 数目。表 4-5 列出了不同的默认值。

表4-5:不同OS和机器上的默认编译器

操作系统 默认编译器
Windows 32 位,任意数量的 CPU -client
Windows 64 位,任意数量的 CPU -server
MacOS,任意数量的 CPU -server
Linux/Solaris 32 位,1 个 CPU -client
Linux/Solaris 32 位,2 个或以上的 CPU -server
Linux 64 位,任意数量的 CPU -server
Solaris 32 位 /64 位 overlay,1 个 CPU -client
Solaris 32 位 /64 位 overlay,2 个或以上的 CPU -server(32 位模式)

确定默认的编译器

想确定所安装的 Java 的默认编译器,可以运行以下命令:

  1. % java -version
  2. java version"1.7.0"
  3. Java(TM) SE Runtime Environment (build 1.7.0-b147)
  4. Java HotSpot(TM) Server VM (build 21.0-b17, mixed mode)

上述示例来自我的 Linux 桌面系统,32 位 Java。最后一行表示所用的编译器为以下三种之一:client(32 位)、server(32 位)或 server(64 位)。 如果所安装的 Java 不支持特定的编译器,最后一行将显示实际使用的编译器:

  1. % java -client -version
  2. java version"1.7.0"
  3. Java(TM) SE Runtime Environment (build 1.7.0-b147)
  4. Java HotSpot(TM) 64-Bit Server VM (build 21.0-b17, mixed mode)

这个例子用的是 64 位 Java,在 Linux 上只支持 64 位 server 编译器。

默认值是基于一个理念的,即启动时间对 32 位 Windows 机器来说是最重要的,而基于 Unix 的系统一般来说更关注长期运行的性能。一如既往,这其中也有例外:确切来说,现代 Windows 也可以运行在高性能服务器上,即便是 32 位模式,这种情况下也应该使用 server 编译器。与此类似,许多应用服务器使用简单的 Java 管理命令侦测或变更配置,即使在 Unix 机器上,这些命令用 client 编译器也很快。

4.3 Java和JIT编译器版本 - 图1 快速小结

1. 不同的 Java 支持不同的编译器。

2. 不同的操作系统和架构所支持的编译器也不相同。

3. 程序不必指定编译器,而是仰仗平台所支持的编译器。