b = obj.equals(otherObj)
的时候,需要查询该equals方法定义在哪个类型上,因为equals方法可能存在于继承树上的任意一个类。如果这段代码被会执行很多次,那么查询操作会耗费很多时间。而在JVM运行这段代码的时候,也许会发现equals方法定义在String类型上,那么当JIT编译器编译这段代码的时候,就会直接调用String类型上的equals方法(当然,在JIT编译得到的代码中,也会考虑到当obj的引用发生变化的时候,需要再次进行查询)。此时,这段代码会在两个方面被优化:
-client
指定,服务器版的使用:-server
。一组数据:
Application | -client | -server | -XX:+TieredCompilation | 类数量 |
---|---|---|---|---|
HelloWorld | 0.08s | 0.08s | 0.08s | Few |
NetBeans | 2.83s | 3.92s | 3.07s | ~10000 |
HelloWorld | 51.5s | 54.0s | 52.0s | ~20000 |
对于批处理任务,任务量的大小是决定运行时间和使用哪种编译策略的最重要因素:
Number of Tasks | -client | -server | -XX:+TieredCompilation |
---|---|---|---|
1 | 0.142s | 0.176s | 0.165s |
10 | 0.211s | 0.348s | 0.226s |
100 | 0.454s | 0.674s | 0.472s |
1000 | 2.556s | 2.158s | 1.910s |
10000 | 23.78s | 14.03s | 13.56s |
可以发现几个结论:
对于长时间运行的应用,比如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秒,所以编译器也有机会在次期间做出优化。
从上面的数据可以发现的几个结论:
以上讨论了JIT编译器的Client以及Server版本,但实际上,JIT编译器有三种:
在32-bit的JVM中,最多可以使用两种JIT编译器。 在64-bit的JVM中,只能使用一种,即-d64。(虽然实际上也含有两种,因为在Tiered编译模式下,Client和Server JIT都会被使用到)
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 | 默认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的关系是建立在以下两个事实之上:
对于绝大部分的场景,只设置使用哪种JIT编译器就足够了:-client, -server或者-XX:+TieredCompilation。 对长时间运行的应用,使用Tiered编译方式更好,即使在短时间运行的引用上使用它,性能也和使用Client编译器类似。
但是在另外一些场合下,还是需要进行另外一些调优。
当JVM对代码进行编译后,被编译的代码以汇编指令的形式存在于代码缓存中(Code Cache),显然这个缓存区域也是有大小限制的,当此区域被填满了之后,编译器就不能够再编译其他Java字节码了。
所以当此区域设置的太小时,会对程序性能造成影响,因为编译器不会对Java字节码进行编译来得到运行速度更快的汇编指令/二进制代码了。
当使用Tiered编译策略,这种影响会更常见。因为该策略在运行之处,编译器的行为类似Client编译器,此时大量的Java字节码会被编译,如果Code Cache设置的太小,那么性能就得不到充分地提升。
当Code Cache区域被填满时,JVM会给出警告:
Java HotSpot(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平台 | 默认空间 |
---|---|
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工具来进行监测。
它们是JVM中两个很比较重要的概念,在Code Cache,Java堆以及各种JVM使用的内存区域中都会出现。
原文:http://blog.csdn.net/dm_vincent/article/details/39529941