您的位置:首页 > 编程语言 > Java开发

Java GC(5)-性能调优的原则

2016-05-19 11:40 411 查看
       本篇为JavaGC的第五篇,本文将讲解Java程序性能调优的原则,尤其是在这个过程中必要的知识以及判断你的程序是否需要调优。还会介绍调优过程中你可能遇到的问题。本文最后会给出一些建议,依据这些你能在对Java程序调优时做出更好的决策。

1、概述

        并不是每个程序都需要调优。如果一个程序性能表现和预期一样,你不必付出额外的精力去提高它的性能。然而,在程序调试完成之后,很难马上就满足它的性能需求,于是就有了调优这项工作。无论哪种编程语言,对应用程序进行调优都需要丰富的技术知识并且注意力高度集中。另外,你也不应该用相同的方式对两个程序调优,因为每个程序都有它自己独特的运作方式和不同的资源使用方式。正因如此,调优比写程序需要更多基础知识。例如,你需要熟悉虚拟机、操作系统和计算机架构。而当你面对在这些知识基础上编写的程序时,就能成功地对它进行调优。

有时调优Java程序只需要修改JVM参数,比如GC的参数。但也有些时候需要修改程序代码。无论那种方法,你首先都需要监控执行Java程序的进程。因此本文会讲解下面几个问题:

怎样监控Java程序?
应该给JVM设置怎样的参数?
如何确定是否需要修改代码?

2、对Java程序进行调优的必要知识

        Java程序在Java虚拟机中运行。因此为了进行调优,你需要理解JVM的工作流程。有一篇博文Understanding
JVM Internals,将让你对JVM有深入的了解。本文中有关JVM运作过程的知识主要关于GC和Hotspot。尽管只有这两方面的知识可能无法对所有的Java程序进行调优,但是这两个因素在大多数情况下都影响着Java程序的性能。

        值得注意的是,从操作系统的角度来看,JVM也是一个应用程序进程。为了给JVM创造良好的运行环境,你还需要对操作系统分配资源的过程有所了解。这意味着,想要调优Java程序,除了JVM你也应该理解操作系统或者硬件的工作方式。

        需要具有的知识还有Java这门语言本身。另外理解锁和并发、类加载和对象创建都是非常重要的。

        当开始调优Java程序时,你应该整合以上各方面的知识来完成工作。

3、Java程序性能调优的过程

图1是一张Java程序性能调优的流程图,摘自由Charlie Hunt和Binu John所著的Java Performance。





图1:Java程序性能调优的过程

4、JVM分布式模型

       JVM分布式模型用于决定是在一个JVM还是多个JVM上执行Java程序。你可以根据其有效性、响应能力和可维护性来进行选择。当在多台服务器上运行JVM时,你也可以选择将多个JVM运行于一台服务器或者每台服务器运行一个JVM。例如,对于每台服务器,你可以运行一个使用8GB堆内存的JVM,也可以运行4个使用2GB的JVM。你理应根据处理器内核的个数还有程序的特性来决定这个数量。当优先考虑响应能力时, 使用2GB的堆内存会优于8GB的,原因是这样能在更短的时间内完成Full
GC。当然,8GB的堆内存可以降低Full GC的频率。如果你的程序使用了内部缓存,还可以通过增加缓存命中率来提高响应能力。综上所述,选择合适的模型需要考虑应用程序的特性,然后在各种模型中 选定一个能够扬长避短的。

5、JVM架构

         选择JVM其实就是决定使用32位还是64位的JVM。在相同的条件下,你最好用32位的。因为32位的JVM比64位性能更好。然而,32位 JVM最大支持的堆内存是4GB(无论在32位操作系统还是64位的上,实际可分配的大小都只有2-3GB)。如果需要更大的堆内存,还是用64位的 JVM比较合适。

表1:性能比较(数据来源

测试基准时间(秒)系数
C++ Opt231.0x
C++ Dbg1978.6x
Java 64-bit1345.8x
Java 32-bit29012.6x
Java 32-bit GC*1064.6x
Java 32-bit SPEC GC*893.7x
Scala823.6x
Scala low-level*672.9x
Scala low-level GC*582.5x
Go 6g1617.0x
Go Pro*1265.5x
        下一步就是运行程序来测试它的性能。这个过程包括GC调优、改变操作系统设置和修改代码。对于这些工作,你可以使用系统监视工具或者性能分析工具。

        注意:针对响应能力的调优和针对吞吐量的调优可能使用不同的方法。如果经常性地发生stop-the-word(串行GC暂时中断程序执行),程序的响应能力就会被降低。比如在高吞吐量时执行Full
GC。不要忘记,在调优时往往有得有失。这样需要折衷处理的事情不仅发生在响应能力和吞吐量之间。例如使用更多的CPU资源来降低内存的使用,或者不得不忍受响应能力和吞吐量其中一个性能指标的下降。相反的情况同样可能发生,实际的调优应该根据各指标的优先级来执行。

        上面图1中的流程展示了几乎可用于所有Java程序的性能调优过程,包括Swing应用。然而,对于我们公司NHN用于提供网络服务的服务器端程序来说,这个方法多少有些不合适。下面图2中的流程是根据图1修改而来,它更简单,也更适合NHN。





图2:对HNH的Java程序的调优过程

        其中,Select JVM表示尽可能使用32位的JVM,除非你需要用64位的JVM来维护一个数GB的缓存。

        现在,跟随图2中的流程,你会了解到每一步具体的工作。

6、JVM参数

         我们会主要讲解如何为Web服务端程序设置合适的JVM参数。尽管不一定适合所有的案例,但是最好的GC算法是Concurrent
Mark Sweep(CMS垃圾回收),特别是对于Web服务端程序。因为低延迟是非常重要的。当然,在使用CMS时,由于新生代空间(New Area)的分配,可能发生较长时间的stop-the-world现象,不过调整新生代空间的大小或者它和整个堆空间的比例可能解决这个问题。

         指定新生代空间的大小和指定整个对堆内存的大小同样重要。你最好使用
–XX:NewRatio
来指定新生代和整个堆的大小比例,或者直接用
–XX:NewSize
来指定所需的新生代空间。这个配置是非常必要的,因为大部分对象都不会存活很久。在Web程序中,除了缓存数据,其他多数对象都只在
HttpRequest
HttpResponse
期间创建。这个时间几乎不会超过1秒,表示这些对象的存活时间也不会超过1秒。如果新生代空间不够大,对象会被转移到老年代空间,以便腾出地方给新对象使用。老年代空间(Old
Area)垃圾回收的代价是比新生代空间大的多的,因此很需要设置一个充足的新生代空间。

          然而,当新生代空间的大小超过一个特定的水平,程序的响应能力会被降低。因为新生代空间的垃圾回收过程,基本上是将数据从一个Survivor Area复制到另外一个(From Space和To Space)。另外,stop-the-world的现象在新生代空间和老年代空间执行垃圾回收时都会发生。如果新生代空间变大,那么Survivor Area的空间也会更大,于是每次复制的数据就更多。基于这样一种特性,我们应该通过指定不同操作系统中HotSpot
JVM的
NewRatio
参数来分配合适大小的新生代空间。

表2:不同操作系统和配置下
NewRatio
的默认值


操作系统及参数默认-XX:NewRatio
Sparc -server2
Sparc -client8
x86 -server8
x86 -client12
        如果设置了
NewRatio
,那么整个堆空间的
1/(NewRatio +1)
就是新生代空间的大小。上表可以看出Sparc -server的NewRatio默认值很小,因为相比x86的操作系统,Sparc以前更多用于高端应用,这个值就是为它们设置的。但现在x86操作系统的性能有很大提升,使用它们作为服务器已经很普遍了。因此指定NewRatio为2或者3是更好的选择,就和Sparc
-server
上的配置一样。

        另外,你还可以通过指定
NewSize
MaxNewSize
来代替NewRatio。那么新生代空间创建时的大小就是指定的NewSize,随后可以一直增长到MaxNewSize的值。Eden(新创建对象存放的区域)和Survivor Area两个区域会随比例增加。就和你为-Xms(译者注:原文是-Xs,应该是笔误)和-Xmx设置相同的值一样,将MaxSize和
MaxNewSize设置为相同的也是一个好选择。

         如果同时指定了NewRatio和NewSize,你应该使用更大的那个。于是,当堆空间被创建时,你可以用过下面的表达式计算初始新生代空间的大小:

[code= java; gutter: true">
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: