分享

[Java Performance] JIT编译器简介

 流浪的星星318 2017-05-10

长时间运行应用的优化

对于长时间运行的应用,比如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编译器。

总结

  1. 对于长时间运行的应用,总是使用-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编译器的优势更明显。

总结

  1. 32位和64位的JVM支持的JIT编译器是不一样的。
  2. 在不同的OS和架构(32bit/64bit)对于JIT编译器的支持是不一样的。
  3. 即使声明了要使用某种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会给出警告:

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平台和默认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使用的内存区域中都会出现。

总结

  1. 代码缓存会影响到JVM能够编译的Java字节码数量,它会被设置一个最大的可用空间,当空间完全被占用后,JIT编译器就会停止编译。
  2. 使用Tiered编译策略时,代码缓存会很快就被用尽(尤其是Java 7),此时可以通过设置预留空间(也就是最大的可用空间)来进行调整。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多