长时间运行应用的优化
对于长时间运行的应用,比如Servlet程序等,一般会使用吞吐量来测试它们的性能。 以下的一组数据表示了一个典型的数据获取程序在使用不同“热身时间”以及不同编译策略时,对吞吐量(OPS)的影响(执行时间为60s):
Warm-up Period
-client
-server
-XX:+TieredCompilation
0s
15.87
23.72
24.23
60s
16.00
23.73
24.26
300s
16.85
24.42
24.43
即使当“热身时间”为0秒,因为执行时间为60秒,所以编译器也有机会在次期间做出优化。
从上面的数据可以发现的几个结论:
对于典型的数据获取程序,编译器对代码编译和优化发生的十分迅速,当“热身时间”显著增加时,如从60秒增加到300秒,最后得到的OPS差异并不明显。
-server JIT编译器和Tiered编译的性能显著优于-client JIT编译器。
总结
对于长时间运行的应用,总是使用-server JIT编译器或者Tiered编译策略。
Java和JIT编译器版本
以上讨论了JIT编译器的Client以及Server版本,但实际上,JIT编译器有三种:
32-bit Client (-client)
32-bit Server (-server)
64-bit Server (-d64)
在32-bit的JVM中,最多可以使用两种JIT编译器。 在64-bit的JVM中,只能使用一种,即-d64。(虽然实际上也含有两种,因为在Tiered编译模式下,Client和Server JIT都会被使用到)
关于32位或者64为JVM的选择
如果你的OS是32位的,那么必须选择32位的JVM;如果你的OS是64位的,那么你可以选择32位或者64为的JVM。
如果你的计算机的内存小于3GB,那么使用32位的JVM性能更优。因为此时JVM中对于内存的引用也是32位的(也就是声明一个指向内存的变量会占用32位的空间),所以操作这些引用的速度也会更快。
使用32位版本的缺点如下:
可用的内存小于4GB,在某些Windows OS中是小于3GB,在某些旧版本的Linux中是小于3.5GB。
对于double和long变量类型的操作速度会慢于64位版本,因为它们不能使用CPU的64位寄存器。
在通常情况下,如果你的程序对于内存的容量要求不那么高,且并不含有很多对于long和double类型的操作,那么选择32位往往更快,和64位相比,性能往往会提高5%-20%不等。
OS和编译器参数之间的关系
JVM版本
-client
-server
-d64
Linux 32-bit
32-bit client compiler
32-bit server compiler
Error
Linux 64-bit
64-bit server compiler
64-bit server compiler
64-bit server compiler
Mac OS X
64-bit server compiler
64-bit server compiler
64-bit server compiler
Solaris 32-bit
32-bit client compiler
32-bit server compiler
Error
Solaris 64-bit
32-bit client compiler
32-bit server compiler
64-bit server compiler
Windows 32-bit
32-bit client compiler
32-bit server compiler
Error
Windows 64-bit
64-bit server compiler
64-bit server compiler
64-bit server compiler
OS和默认编译器的关系
OS
默认JIT编译器
Windows, 32-bit, any number of CPUs
-client
Windows, 64-bit, any number of CPUs
-server
Mac OS X, any number of CPUs
-server
Linux/Solaris, 32-bit, 1 CPU
-client
Linux/Solaris, 32-bit, 2 or more CPUs
-server
Linux, 64-bit, any number of CPUs
-server
Solaris, 32-bit/64-bit overlay, 1 CPU
-client
Solaris, 32-bit/64-bit overlay, 2 or more CPUs
-server (32-bit mode)
OS和默认JIT的关系是建立在以下两个事实之上:
当Java程序运行在Windows 32位的计算机上时,程序的启动速度往往是最重要的,因为它面向的往往是最终用户。
当Java程序运行在Unix/Linux系统上时,程序往往是长时间运行的服务端程序,所以Server JIT编译器的优势更明显。
总结
32位和64位的JVM支持的JIT编译器是不一样的。
在不同的OS和架构(32bit/64bit)对于JIT编译器的支持是不一样的。
即使声明了要使用某种JIT编译器,根据运行时平台的不同,实际使用的也不一定是指定的编译器。
JIT编译器调优进阶
对于绝大部分的场景,只设置使用哪种JIT编译器就足够了:-client, -server或者-XX:+TieredCompilation。 对长时间运行的应用,使用Tiered编译方式更好,即使在短时间运行的引用上使用它,性能也和使用Client编译器类似。
但是在另外一些场合下,还是需要进行另外一些调优。
代码缓存调优(Tuning
the Code Cache)
当JVM对代码进行编译后,被编译的代码以汇编指令的形式存在于代码缓存中(Code Cache),显然这个缓存区域也是有大小限制的,当此区域被填满了之后,编译器就不能够再编译其他Java字节码了。
所以当此区域设置的太小时,会对程序性能造成影响,因为编译器不会对Java字节码进行编译来得到运行速度更快的汇编指令/二进制代码了。
当使用Tiered编译策略,这种影响会更常见。因为该策略在运行之处,编译器的行为类似Client编译器,此时大量的Java字节码会被编译,如果Code Cache设置的太小,那么性能就得不到充分地提升。
当Code Cache区域被填满时,JVM会给出警告:
JavaHotSpot(TM) 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled. Java HotSpot(TM) 64-Bit Server VM warning: Try increasing the code cache size using -XX:ReservedCodeCacheSize=
当然通过查看编译日志也能够知道Code Cache的使用情况。
Java平台和默认Code Cache空间的关系:
Java平台
默认空间
32-bit client, Java 8
32 MB
32-bit server with tiered compilation, Java 8
240 MB
64-bit server with tiered compilation, Java 8
240 MB
32-bit client, Java 7
32 MB
32-bit server, Java 7
32 MB
64-bit server, Java 7
48 MB
64-bit server with tiered compilation, Java 7
96 MB
在Java 7中,这个区域的默认空间经常不够,所以在必要的场景下需要增加它的空间。然而,并没有一个非常好的方法来给出一个应用到底在Code Cache区域的空间为多少时才能够达到最好的性能。你所能做的就是不断地进行尝试,来得到一个最好的结果。
Code Cache的最大空间可以通过:-XX:ReservedCodeCacheSize=N来进行设置。在默认情况下,N就是上表中的默认空间大小。关于Code Cache的空间管理,和JVM中对于其他内存空间的管理方法类似,也提供了一个设置初始值的方法:-XX:InitialCodeCacheSize=N。初始值和选择编译器的类型以及处理器的架构相关,但是一般需要设置的只是最大空间。
代码缓存空间的大小
那么是不是把Code Cache空间设置的越大越好呢?也不尽然。因为设置之后,哪怕实际上没有用到,这块空间也被JVM给“预定”了,不能用作他途。
前文中提到过,如果JVM使用的是32位的,那么内存空间最大为4GB,这个4GB包括了Java堆,JVM自身的代码(包括用到的各种Native库和线程栈),应用程序可分配的内存空间,当然也会包括Code Cache。所以从这个角度出发,也不是把它设置的越大越好。
在程序运行时,可以通过jconsole工具来进行监测。
预定内存和分配内存(Reserved
Memory and Allocation Memory)
它们是JVM中两个很比较重要的概念,在Code Cache,Java堆以及各种JVM使用的内存区域中都会出现。
总结
代码缓存会影响到JVM能够编译的Java字节码数量,它会被设置一个最大的可用空间,当空间完全被占用后,JIT编译器就会停止编译。
使用Tiered编译策略时,代码缓存会很快就被用尽(尤其是Java 7),此时可以通过设置预留空间(也就是最大的可用空间)来进行调整。