彩世界平台-彩世界时时app-彩世界开奖app苹果下载

热门关键词: 彩世界平台,彩世界时时app,彩世界开奖app苹果下载

您的位置:彩世界平台 > 活动会议 > jvm种类(九):怎样优化Java GC「译」,jvm

jvm种类(九):怎样优化Java GC「译」,jvm

发布时间:2019-09-05 07:04编辑:活动会议浏览(55)

    jvm系列(九):如何优化Java GC「译」,jvm

    本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作。

    Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章的第三篇《How to Tune Java Garbage Collection》,本文的作者是韩国人,写在JDK 1.8发布之前,虽然有些地方有些许过时,但整体内容还是非常有价值的。译者此前也看到有人翻译了本文,发现其中有许多错漏生硬和语焉不详之处,因此决定自己翻译一份,供大家分享。

    本文是“成为Java GC专家”系列文章的第三篇,在系列的第一篇文章《理解Java GC》中,我们了解到了不同GC算法的执行过程、GC的工作原理、新生代和老年代的概念、JDK 7中你需要了解的5种GC类型以及每一种GC对性能的影响。

    在系列的第二篇文章《如何监控Java GC》中笔者已经解释了JVM进行实时GC的原理、监控GC的方法以及可以使这一过程更加迅速高效的工具。

    在第三篇文章中,笔者将基于实际生产环境中的案例,介绍几个GC优化的最佳参数设置。在此我们假设你已经理解了本系列前两篇文章的内容,因此为了更深入的理解本文所讲内容,我建议你在阅读本篇文章之前先仔细阅读这两篇文章。

    本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作。

    GC优化是必要的吗?

    或者更准确地说,GC优化对Java基础服务来说是必要的吗?答案是否定的,事实上GC优化对Java基础服务来说在有些场合是可以省去的,但前提是这些正在运行的Java系统,必须包含以下参数或行为:

    • 内存大小已经通过-Xms-Xmx参数指定过
    • 运行在server模式下(使用-server参数)
    • 系统中没有残留超时日志之类的错误日志

    换句话说,如果你在运行时没有手动设置内存大小并且打印出了过多的超时日志,那你就需要对系统进行GC优化。

    不过你需要时刻谨记一句话:GC tuning is the last task to be done.

    现在来想一想GC优化的最根本原因,垃圾收集器的工作就是清除Java创建的对象,垃圾收集器需要清理的对象数量以及要执行的GC数量均取决于已创建的对象数量。因此,为了使你的系统在GC上表现良好,首先需要减少创建对象的数量。

    俗话说“冰冻三尺非一日之寒”,我们在编码时要首先要把下面这些小细节做好,否则一些琐碎的不良代码累积起来将让GC的工作变得繁重而难于管理:

    • 使用StringBuilderStringBuffer来代替String
    • 尽量少输出日志

    尽管如此,仍然会有我们束手无策的情况。XML和JSON解析过程往往占用了最多的内存,即使我们已经尽可能地少用String、少输出日志,仍然会有大量的临时内存(大约10-100MB)被用来解析XML或JSON文件,但我们又很难弃用XML和JSON。在此,你只需要知道这一过程会占据大量内存即可。

    如果在经过几次重复的优化后应用程序的内存用量情况有所改善,那么久可以启动GC优化了。

    笔者总结了GC优化的两个目的:

    1. 将进入老年代的对象数量降到最低
    2. 减少Full GC的执行时间

    Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三篇《How to Tune Java Garbage Collection》,本文的作者是韩国人,写在JDK 1.8发布之前,虽然有些地方有些许过时,但整体内容还是非常有价值的。译者此前也看到有人翻译了本文,发现其中有许多错漏生硬和语焉不详之处,因此决定自己翻译一份,供大家分享。

    将进入老年代的对象数量降到最低

    除了可以在JDK 7及更高版本中使用的G1收集器以外,其他分代GC都是由Oracle JVM提供的。关于分代GC,就是对象在Eden区被创建,随后被转移到Survivor区,在此之后剩余的对象会被转入老年代。也有一些对象由于占用内存过大,在Eden区被创建后会直接被传入老年代。老年代GC相对来说会比新生代GC更耗时,因此,减少进入老年代的对象数量可以显著降低Full GC的频率。你可能会以为减少进入老年代的对象数量意味着把它们留在新生代,事实正好相反,新生代内存的大小是可以调节的。

    本文是“成为Java GC专家”系列文章的第三篇,在系列的第一篇文章《理解Java GC》中,我们了解到了不同GC算法的执行过程、GC的工作原理、新生代和老年代的概念、JDK 7中你需要了解的5种GC类型以及每一种GC对性能的影响。

    降低Full GC的时间

    Full GC的执行时间比Minor GC要长很多,因此,如果在Full GC上花费过多的时间(超过1s),将可能出现超时错误。

    • 如果通过减小老年代内存来减少Full GC时间,可能会引起OutOfMemoryError或者导致Full GC的频率升高。
    • 另外,如果通过增加老年代内存来降低Full GC的频率,Full GC的时间可能因此增加。

    因此,你需要把老年代的大小设置成一个“合适”的值

    在系列的第二篇文章《如何监控Java GC》中笔者已经解释了JVM进行实时GC的原理、监控GC的方法以及可以使这一过程更加迅速高效的工具。

    影响GC性能的参数

    正如我在系列的第一篇文章《理解Java GC》末尾提到的,不要幻想着“如果有人用他设置的GC参数获取了不错的性能,我们为什么不复制他的参数设置呢?”,因为对于不用的Web服务,它们创建的对象大小和生命周期都不相同。

    举一个简单的例子,如果一个任务的执行条件是A,B,C,D和E,另一个完全相同的任务执行条件只有A和B,那么哪一个任务执行速度更快呢?作为常识来讲,答案很明显是后者。

    Java GC参数的设置也是这个道理,设置好几个参数并不会提升GC执行的速度,反而会使它变得更慢。GC优化的基本原则是将不同的GC参数应用到两个及以上的服务器上然后比较它们的性能,然后将那些被证明可以提高性能或减少GC执行时间的参数应用于最终的工作服务器上。

    下面这张表展示了与内存大小相关且会影响GC性能的GC参数

    表1:GC优化需要考虑的JVM参数

    类型 参数 描述
    堆内存大小 -Xms 启动JVM时堆内存的大小
    -Xmx 堆内存最大限制
    新生代空间大小 -XX:NewRatio 新生代和老年代的内存比
    -XX:NewSize 新生代内存大小
    -XX:SurvivorRatio Eden区和Survivor区的内存比

    笔者在进行GC优化时最常用的参数是-Xms,-Xmx-XX:NewRatio-Xms-Xmx参数通常是必须的,所以NewRatio的值将对GC性能产生重要的影响。

    有些人可能会问如何设置永久代内存大小,你可以用-XX:PermSize-XX:MaxPermSize参数来进行设置,但是要记住,只有当出现OutOfMemoryError错误时你才需要去设置永久代内存。

    还有一个会影响GC性能的因素是垃圾收集器的类型,下表展示了关于GC类型的可选参数(基于JDK 6.0):

    表2:GC类型可选参数

    GC类型 参数 备注
    Serial GC -XX:+UseSerialGC
    Parallel GC -XX:+UseParallelGC-XX:ParallelGCThreads=value
    Parallel Compacting GC -XX:+UseParallelOldGC
    CMS GC -XX:+UseConcMarkSweepGC-XX:+UseParNewGC-XX:+CMSParallelRemarkEnabled-XX:CMSInitiatingOccupancyFraction=value-XX:+UseCMSInitiatingOccupancyOnly
    G1 -XX:+UnlockExperimentalVMOptions-XX:+UseG1GC 在JDK 6中这两个参数必须配合使用

    除了G1收集器外,可以通过设置上表中每种类型第一行的参数来切换GC类型,最常见的非侵入式GC就是Serial GC,它针对客户端系统进行了特别的优化。

    会影响GC性能的参数还有很多,但是上述的参数会带来最显著的效果,请切记,设置太多的参数并不一定会提升GC的性能。

    在第三篇文章中,笔者将基于实际生产环境中的案例,介绍几个GC优化的最佳参数设置。在此我们假设你已经理解了本系列前两篇文章的内容,因此为了更深入的理解本文所讲内容,我建议你在阅读本篇文章之前先仔细阅读这两篇文章。

    GC优化的过程

    GC优化的过程和大多数常见的提升性能的过程相似,下面是笔者使用的流程:

    GC优化是必要的吗?

    或者更准确地说,GC优化对Java基础服务来说是必要的吗?答案是否定的,事实上GC优化对Java基础服务来说在有些场合是可以省去的,但前提是这些正在运行的Java系统,必须包含以下参数或行为:

    • 内存大小已经通过-Xms-Xmx参数指定过
    • 运行在server模式下(使用-server参数)
    • 系统中没有残留超时日志之类的错误日志

    换句话说,如果你在运行时没有手动设置内存大小并且打印出了过多的超时日志,那你就需要对系统进行GC优化。

    不过你需要时刻谨记一句话:GC tuning is the last task to be done.

    现在来想一想GC优化的最根本原因,垃圾收集器的工作就是清除Java创建的对象,垃圾收集器需要清理的对象数量以及要执行的GC数量均取决于已创建的对象数量。因此,为了使你的系统在GC上表现良好,首先需要减少创建对象的数量。

    俗话说“冰冻三尺非一日之寒”,我们在编码时要首先要把下面这些小细节做好,否则一些琐碎的不良代码累积起来将让GC的工作变得繁重而难于管理:

    • 使用StringBuilderStringBuffer来代替String
    • 尽量少输出日志

    尽管如此,仍然会有我们束手无策的情况。XML和JSON解析过程往往占用了最多的内存,即使我们已经尽可能地少用String、少输出日志,仍然会有大量的临时内存(大约10-100MB)被用来解析XML或JSON文件,但我们又很难弃用XML和JSON。在此,你只需要知道这一过程会占据大量内存即可。

    如果在经过几次重复的优化后应用程序的内存用量情况有所改善,那么久可以启动GC优化了。

    笔者总结了GC优化的两个目的:

    1. 将进入老年代的对象数量降到最低
    2. 减少Full GC的执行时间

    1.监控GC状态

    你需要监控GC从而检查系统中运行的GC的各种状态,具体方法请查看系列的第二篇文章《如何监控Java GC》

    将进入老年代的对象数量降到最低

    除了可以在JDK 7及更高版本中使用的G1收集器以外,其他分代GC都是由Oracle JVM提供的。关于分代GC,就是对象在Eden区被创建,随后被转移到Survivor区,在此之后剩余的对象会被转入老年代。也有一些对象由于占用内存过大,在Eden区被创建后会直接被传入老年代。老年代GC相对来说会比新生代GC更耗时,因此,减少进入老年代的对象数量可以显著降低Full GC的频率。你可能会以为减少进入老年代的对象数量意味着把它们留在新生代,事实正好相反,新生代内存的大小是可以调节的。

    2.分析监控结果后决定是否需要优化GC

    在检查GC状态后,你需要分析监控结构并决定是否需要进行GC优化。如果分析结果显示运行GC的时间只有0.1-0.3秒,那么就不需要把时间浪费在GC优化上,但如果运行GC的时间达到1-3秒,甚至大于10秒,那么GC优化将是很有必要的。

    但是,如果你已经分配了大约10GB内存给Java,并且这些内存无法省下,那么就无法进行GC优化了。在进行GC优化之前,你需要考虑为什么你需要分配这么大的内存空间,如果你分配了1GB或2GB大小的内存并且出现了OutOfMemoryError,那你就应该执行堆转储(heap dump)来消除导致异常的原因。

    注意:

    堆转储(heap dump)是一个用来检查Java内存中的对象和数据的内存文件。该文件可以通过执行JDK中的jmap命令来创建。在创建文件的过程中,所有Java程序都将暂停,因此,不要再系统执行过程中创建该文件。

    你可以在互联网上搜索heap dump的详细说明。对于韩国读者,可以直接参考我去年发布的书:《The story of troubleshooting for Java developers and system operators》 (Sangmin Lee, Hanbit Media, 2011, 416 pages)

    降低Full GC的时间

    Full GC的执行时间比Minor GC要长很多,因此,如果在Full GC上花费过多的时间(超过1s),将可能出现超时错误。

    • 如果通过减小老年代内存来减少Full GC时间,可能会引起OutOfMemoryError或者导致Full GC的频率升高。
    • 另外,如果通过增加老年代内存来降低Full GC的频率,Full GC的时间可能因此增加。

    因此,你需要把老年代的大小设置成一个“合适”的值

    3.设置GC类型/内存大小

    如果你决定要进行GC优化,那么你需要选择一个GC类型并且为它设置内存大小。此时如果你有多个服务器,请如上文提到的那样,在每台机器上设置不同的GC参数并分析它们的区别。

    影响GC性能的参数

    正如我在系列的第一篇文章《理解Java GC》末尾提到的,不要幻想着“如果有人用他设置的GC参数获取了不错的性能,我们为什么不复制他的参数设置呢?”,因为对于不用的Web服务,它们创建的对象大小和生命周期都不相同。

    举一个简单的例子,如果一个任务的执行条件是A,B,C,D和E,另一个完全相同的任务执行条件只有A和B,那么哪一个任务执行速度更快呢?作为常识来讲,答案很明显是后者。

    Java GC参数的设置也是这个道理,设置好几个参数并不会提升GC执行的速度,反而会使它变得更慢。GC优化的基本原则是将不同的GC参数应用到两个及以上的服务器上然后比较它们的性能,然后将那些被证明可以提高性能或减少GC执行时间的参数应用于最终的工作服务器上。

    下面这张表展示了与内存大小相关且会影响GC性能的GC参数

    表1:GC优化需要考虑的JVM参数

    类型 参数 描述
    堆内存大小 -Xms 启动JVM时堆内存的大小
    -Xmx 堆内存最大限制
    新生代空间大小 -XX:NewRatio 新生代和老年代的内存比
    -XX:NewSize 新生代内存大小
    -XX:SurvivorRatio Eden区和Survivor区的内存比

    笔者在进行GC优化时最常用的参数是-Xms,-Xmx-XX:NewRatio-Xms-Xmx参数通常是必须的,所以NewRatio的值将对GC性能产生重要的影响。

    有些人可能会问如何设置永久代内存大小,你可以用-XX:PermSize-XX:MaxPermSize参数来进行设置,但是要记住,只有当出现OutOfMemoryError错误时你才需要去设置永久代内存。

    还有一个会影响GC性能的因素是垃圾收集器的类型,下表展示了关于GC类型的可选参数(基于JDK 6.0):

    表2:GC类型可选参数

    GC类型 参数 备注
    Serial GC -XX:+UseSerialGC
    Parallel GC -XX:+UseParallelGC-XX:ParallelGCThreads=value
    Parallel Compacting GC -XX:+UseParallelOldGC
    CMS GC -XX:+UseConcMarkSweepGC-XX:+UseParNewGC-XX:+CMSParallelRemarkEnabled-XX:CMSInitiatingOccupancyFraction=value-XX:+UseCMSInitiatingOccupancyOnly
    G1 -XX:+UnlockExperimentalVMOptions-XX:+UseG1GC 在JDK 6中这两个参数必须配合使用

    除了G1收集器外,可以通过设置上表中每种类型第一行的参数来切换GC类型,最常见的非侵入式GC就是Serial GC,它针对客户端系统进行了特别的优化。

    会影响GC性能的参数还有很多,但是上述的参数会带来最显著的效果,请切记,设置太多的参数并不一定会提升GC的性能。

    4.分析结果

    在设置完GC参数后就可以开始收集数据,请在收集至少24小时后再进行结果分析。如果你足够幸运,你可能会找到系统的最佳GC参数。如若不然,你还需要分析输出日志并检查分配的内存,然后需要通过不断调整GC类型/内存大小来找到系统的最佳参数。

    GC优化的过程

    GC优化的过程和大多数常见的提升性能的过程相似,下面是笔者使用的流程:

    5.如果结果令人满意,将参数应用到所有服务器上并结束GC优化

    如果GC优化的结果令人满意,就可以将参数应用到所有服务器上,并停止GC优化。

    在下面的章节中,你将会看到上述每一步所做的具体工作。

    1.监控GC状态

    你需要监控GC从而检查系统中运行的GC的各种状态,具体方法请查看系列的第二篇文章《如何监控Java GC》

    监控GC状态并分析结果

    在运行中的Web应用服务器(Web Application Server,WAS)上查看GC状态的最佳方式就是使用jstat命令。笔者在《如何监控Java GC》中已经介绍过了jstat命令,所以在本篇文章中我将着重关注数据部分。

    下面的例子展示了某个还没有执行GC优化的JVM的状态(虽然它并不是运行服务器)。

    $ jstat -gcutil 21719 1s
    S0    S1    E    O    P    YGC    YGCT    FGC    FGCT GCT
    48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
    48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
    

    我们先看一下YGC(从应用程序启动到采样时发生 Young GC 的次数)和YGCT(从应用程序启动到采样时 Young GC 所用的时间(秒)),计算YGCT/YGC会得出,平均每次新生代的GC耗时50ms,这是一个很小的数字,通过这个结果可以看出,我们大可不必关注新生代GC对GC性能的影响。

    现在来看一下FGC( 从应用程序启动到采样时发生 Full GC 的次数)和FGCT(从应用程序启动到采样时 Full GC 所用的时间(秒)),计算FGCT/FGC会得出,平均每次老年代的GC耗时19.68s。有可能是执行了三次Full GC,每次耗时19.68s,也有可能是有两次只花了1s,另一次花了58s。不管是哪一种情况,GC优化都是很有必要的。

    使用jstat命令可以很容易地查看GC状态,但是分析GC的最佳方式是加上-verbosegc参数来生成日志。在之前的文章中笔者已经解释了如何分析这些日志。HPJMeter是笔者最喜欢的用于分析-verbosegc生成的日志的工具,它简单易用,使用HPJmeter可以很容易地查看GC执行时间以及GC发生频率。

    此外,如果GC执行时间满足下列所有条件,就没有必要进行GC优化了:

    • Minor GC执行非常迅速(50ms以内)
    • Minor GC没有频繁执行(大约10s执行一次)
    • Full GC执行非常迅速(1s以内)
    • Full GC没有频繁执行(大约10min执行一次)

    括号中的数字并不是绝对的,它们也随着服务的状态而变化。有些服务可能要求一次Full GC在0.9s以内,而有些则会放得更宽一些。因此,对于不同的服务,需要按照不同的标准考虑是否需要执行GC优化。

    当检查GC状态时,不能只查看Minor GC和Full GC的时间,还必须要关注GC执行的次数。如果新生代空间太小,Minor GC将会非常频繁地执行(有时每秒会执行一次,甚至更多)。此外,传入老年代的对象数目会上升,从而导致Full GC的频率升高。因此,在执行jstat命令时,请使用-gccapacity参数来查看具体占用了多少空间。

    2.分析监控结果后决定是否需要优化GC

    在检查GC状态后,你需要分析监控结构并决定是否需要进行GC优化。如果分析结果显示运行GC的时间只有0.1-0.3秒,那么就不需要把时间浪费在GC优化上,但如果运行GC的时间达到1-3秒,甚至大于10秒,那么GC优化将是很有必要的。

    但是,如果你已经分配了大约10GB内存给Java,并且这些内存无法省下,那么就无法进行GC优化了。在进行GC优化之前,你需要考虑为什么你需要分配这么大的内存空间,如果你分配了1GB或2GB大小的内存并且出现了OutOfMemoryError,那你就应该执行堆转储(heap dump)来消除导致异常的原因。

    注意:

    堆转储(heap dump)是一个用来检查Java内存中的对象和数据的内存文件。该文件可以通过执行JDK中的jmap命令来创建。在创建文件的过程中,所有Java程序都将暂停,因此,不要再系统执行过程中创建该文件。

    你可以在互联网上搜索heap dump的详细说明。对于韩国读者,可以直接参考我去年发布的书:《The story of troubleshooting for Java developers and system operators》 (Sangmin Lee, Hanbit Media, 2011, 416 pages)

    本文由彩世界平台发布于活动会议,转载请注明出处:jvm种类(九):怎样优化Java GC「译」,jvm

    关键词: