JIT 编译器

JIT编译器 ( Just-In-Time ) , 是保证java程序运行性能的核心概念.

我们说C++之类的语言是编译型语言,而php或者perl之类的脚本语言一般为解释型语言

对于只运行一次的代码, 解释型语言更为灵活也更快, 因为节约了预处理(编译链接之类)的步骤

对于运行多次的代码, 编译型语言则更快(二进制码的运行速度要优于可读代码)

这里我们知道java实际上是一种混合语言,它会把java代码先编译成字节码,然后在jvm上运行, 但是实际上jvm运行的也不全是java字节码,对于调用次数最多的一些代码,jvm会在运行多次以后将其编译成跟C++一样的二进制码,从而达到更快的运行速度

这里不同的jvm虚拟机的处理方式是不一样的, Oracle的HotSpot JVM只会将”最热”的一部分代码进行彻底编译

1. 编译器的类型

编译器的类型分为两种, 一种是client 另一种是server

你需要使用 java -client 或者 java -server 来使用它们

两种编译器的主要区别在于编译代码的时机不同(这里指的是编译成为二进制码的时机而不是javac编译字节码的时机)

client编译器进行编译的时间要更早,编译的代码也更多,但是编译后的代码运行速度要比server编译器的慢一些

server编译器在编译代码时会进行更好的优化

在C:\Java\jdk1.7.0_40\jre\lib\amd64 下有一个jvm.cfg文件,如果不想通过参数指定的话可以直接修改默认行为

我本地java7的默认内容是

-server KNOWN
-client IGNORE
-hotspot ALIASED_TO -server
-classic WARN
-native ERROR
-green ERROR

而java8的默认内容是

-server KNOWN
-client IGNORE

可以看到因为是64位的jdk,所以 -client参数都被忽略掉了,这里我们把

-client IGNORE换成 -client KNOWN 然后试试 这个参数

实际上你会得到下面的错误

C:\Java\jdk1.7.0_40\bin>java -client -version
Error: missing `client' JVM at `C:\Java\jdk1.7.0_40\jre\bin\client\jvm.dll'.
Please install or use the JRE or JDK that contains these missing components.

当调用java -version时,可以看到当前的jvm模式 (这里是64位server模式)

[root@vagrant ~]# java -version
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)

java7中引入了一种叫做分层编译的行为,不过当时因为有瑕疵所以没有成为默认配置, java8已经将分层编译作为jvm的默认配置

-Xint, -Xcomp, 和 -Xmixed 三个参数分别意味着

-Xint 只编译成字节码,不编译本地代码

-Xcomp 全部编译成本地代码 (但是会损失一部分比如”积极的分支预测”等要等程序运行一段时间才能知道如何优化的部分)

-Xmixed java8的默认配置, 只编译”最热”的部分

-XX:+TieredCompilation 这个参数会告诉虚拟机在一开始使用client 模式, 在运行一段时间后使用server模式重新编译, 不过前提是启动jvm的时候需要使用 -server模式

前三个参数控制是否需要JIT编译,最后一个参数告诉jvm使用何种方式进行JIT编译

结论和优化

如果是32位系统,那你只能使用32位的jvm,这是毋庸置疑的

但是如果你使用64位系统(现在几乎没人使用纯32位系统了吧), 你可以选择使用32位的模式来运行java

当堆小于3GB时, 32位的java会快一些而且占用的内存也会更小,因为jvm将使用32位指针,操作32位指针的代价要少于64位的(即使你使用64位cpu,32位模式在内存不超过3GB的情况下同样性能更好), 在32位jvm上如果程序使用32位寻址,无论cpu是32位的还是64位的,性能都要比64位运行时快5%到20%,可是如果程序使用了大量需要使用64位寄存器的值,比如long和double,32位jvm会比较慢,这里需要注意

32位对象的引用指针占4个字节,而64位的则占用8个字节,这就需要更多GC周期和内存空间

jvm可以使用压缩的oop(ordinary object pointer)来优化这一情况,通过对超出32位的部分地址进行补0 ( 当一个35位指针地址最后3位都是0时, 在堆中会表示成35位, 当它被移动进寄存器的时候, 末尾的3个0会被添加回来)

对于大小在4GB-32GB之间的堆大小, 应用使用压缩的oop, 通过 -XX:+UseCompressedOops来启用,不过在java7和之后的版本中,只要堆大小小于32GB,这个设置默认就是开启的

所以我们的结论是要么使用小于32GB的堆空间,要么使用38GB以上,因为64位指针会多占用20%的内存空间