精华内容
下载资源
问答
  • [Full GC [PSYoungGen:

    服务器日志一起在Full GC,然后就挂了

    [Full GC [PSYoungGen: 105376K->0K(699392K)] [ParOldGen: 1665855K->1771090K(2097152K)] 1771231K->1771090K(2796544K) [PSPermGen: 46723K->46721K(262144K)], 3.8789030 secs] [Times: user=7.58 sys=0.02, real=3.87 secs] 

    [Full GC [PSYoungGen: 350208K->0K(699392K)] [ParOldGen: 1771090K->1875193K(2097152K)] 2121298K->1875193K(2796544K) [PSPermGen: 46721K->46721K(262144K)], 2.9874460 secs] [Times: user=5.88 sys=0.01, real=2.99 secs] 
    [Full GC [PSYoungGen: 350208K->0K(699392K)] [ParOldGen: 1875193K->1980629K(2097152K)] 2225401K->1980629K(2796544K) [PSPermGen: 46721K->46721K(262144K)], 3.0111150 secs] [Times: user=5.94 sys=0.00, real=3.01 secs] 
    [Full GC [PSYoungGen: 350208K->0K(699392K)] [ParOldGen: 1980629K->2085691K(2097152K)] 2330837K->2085691K(2796544K) [PSPermGen: 46721K->46721K(262144K)], 3.1051230 secs] [Times: user=6.11 sys=0.01, real=3.10 secs] 
    [Full GC [PSYoungGen: 343004K->89247K(699392K)] [ParOldGen: 2085691K->2097149K(2097152K)] 2428696K->2186397K(2796544K) [PSPermGen: 46880K->46880K(262144K)], 3.1350740 secs] [Times: user=6.20 sys=0.00, real=3.14 secs] 
    [Full GC [PSYoungGen: 350208K->161438K(699392K)] [ParOldGen: 2097149K->2096682K(2097152K)] 2447357K->2258120K(2796544K) [PSPermGen: 47106K->47105K(262144K)], 3.1200190 secs] [Times: user=6.18 sys=0.01, real=3.11 secs] 

    [Full GC [PSYoungGen: 350208K->208701K(699392K)] [ParOldGen: 2096682K->2096671K(2097152K)] 2446890K->2305373K(2796544K) [PSPermGen: 48045K->48045K(262144K)], 3.2127130 secs] [Times: user=6.35 sys=0.00, real=3.22 secs]

    尝试了各种调优,各种jvm内存分析http://blog.csdn.net/haiyang4988/article/details/54943803、工具使用http://blog.csdn.net/wanghuiqi2008/article/details/50724676等等,就是解决不了问题,折腾了一天半啊一天半。最后,导出.hprof文件分析内存的时候,去看了下日志,结果发现,磁盘满了...............所以猜测是不是磁盘满了导致的full gc,然后清理一下磁盘,果然,没问题了。

    #查询指定进行内存情况
    ps -e -o 'pid,comm,args,pcpu,rsz,vsz,stime,user,uid' | grep java |  sort -nrk5
    #查看gc情况
    nohup jstat -gccause 10351 1s >> ~provider.log &
    #生成dump文件
    jmap -dump:format=b,file=provider.hprof

    不想说什么了,就这样吧骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人骂人

    展开全文
  • 使用 JVM的人都或多或少的了解垃圾回收机制,当系统的服务出现性能问题时,都会去服务器上查看下系统GC的情况。此外,如果有新的服务上线,也需要去服务器上查看下新服务的整体GC水平,这就可以使用jstat命令来查看...

    使用 JVM的人都或多或少的了解垃圾回收机制,当系统的服务出现性能问题时,都会去服务器上查看下系统GC的情况。此外,如果有新的服务上线,也需要去服务器上查看下新服务的整体GC水平,这就可以使用jstat命令来查看了,当然你也可以使用其他方式。

    jstat的命令查看系统GC情况,很简单,只需要先通过jps或者ps -aux |grep tomcat来查看对应服务所在的进程数,然后使用下面命令查看。jstat 查看 gc 使用方法如下:

    jstat -gcutil `线程值` `间隔时间数(ms)`执行命令sudo jstat -gcutil 11694 3600,结果会显示内存各个区域大小的情况,如图:

    3409ea3211696d71ab21cdd9b77b3f9d.pngNote: 结果里面列出每个区间的内存大小,新生代gc的次数和时间,老年代gc的次数和时间。

    1. S0 — Heap上的 Survivor space 0 区已使用空间的百分比;

    2. S1 — Heap上的 Survivor space 1 区已使用空间的百分比;

    3. E — Heap上的 Eden space 区已使用空间的百分比;

    4. O — Heap上的 Old space 区已使用空间的百分比;

    5. P — Perm space 区已使用空间的百分比;

    6. YGC — 从应用程序启动到采样时发生 Young GC 的次数;

    7. YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒);

    8. FGC — 从应用程序启动到采样时发生 Full GC 的次数;

    9. FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒);

    10. GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒).

    当我们使用jstat命令查看GC时,发现YoungGC或者FullGC频率过高时,就需要分析服务的具体情况了。这个时候,可以使用Jmap来查看当前系统的实例使用内存大小,一般地,需要dump到本地进行分析,使用可视化工具可以使分析效率得到很大的提高。Jmap的使用方法如下所示:

    jmap -dump:format=b,file=log.bin `进程id`使用sudo jmap -dump:format=b,file=log.bin -F 11694命令,写入文件内容如下:

    JAVA PROFILE 1.0.1

    JAVA PROFILE 1.0.1

    Lcom/alibaba/dubbo/remoting/exchange/support/DefaultFuture$RemotingInvocationTimeoutScan;

    DefaultStatistics.java

    T8procedureNameRs

    StringUtils.10

    isCons

    (Ljava/lang/Integer;)Ljava/math/BigDecimal;

    HtheCloseBracketFor

    h([Ljava/lang/String;Lorg/codehaus/jackson/JsonGenerator;Lorg/codehaus/jackson/map/SerializerProvider;Lorg/codehaus/jackson/map/JsonSerializer;)V

    [email protected]

    lastActiveFilter

    Ampersand

    ......Note:有很多可视化的工具来分析dump文件,为了简单,这里使用java自带的工具jvisualvm工具,在文件选项中装入 dump 下来的 文件log.bin,就可以看到各个对象实例所占用的内存大小和实例数量了。如下图:

    b6acbaead24aaf12b7f9d6d10aa583a1.png

    java 中 还有一个命令比较有用,就是jstack,该命令可以查看服务器上的某个服务的堆栈信息。从而分析线程是否处于死锁状态,从而导致服务不可用或者服务性能受到影响。使用命令jstack 线程id就可以获取所有堆栈信息了,非常简单便捷。

    展开全文
  • 如何优化Java GC

    千次阅读 2018-07-08 11:19:06
    ​欢迎访问 陈同学博客原文 ... 参考:JVM 调优 — GC 长时间停顿问题及解决方法 本文是 成为Java GC专家 系列的第三篇。 在第一篇 理解 Java GC 中我们学习了不同GC算法的处理过程,GC是如何工作的,什么是...

    ​欢迎访问 陈同学博客原文

    原文:How to Tune Java Garbage Collection by Sangmin Lee ON 06/02/2017
    翻译:陈同学
    参考:JVM 调优 — GC 长时间停顿问题及解决方法

    本文是 成为Java GC专家 系列的第三篇。

    在第一篇 理解 Java GC 中我们学习了不同GC算法的处理过程,GC是如何工作的,什么是年轻代和老年代,JDK7中的5种GC类型,以及每种GC类型对性能的影响。

    在第二篇 如何监控Java GC 中讲述了运行中的JVM如何进行GC,如何监控GC以及一些高效监控GC的工具。

    本文将通过2个真实案例来演示一些你用得上的GC优化参数。本文假定你已理解本系列的前两篇文章,若还未阅读,请先阅读。

    有必要优化GC吗?

    确切的说是 基于Java的应用一定需要进行GC优化吗?我认为并非所有基于Java的应用都需要进行GC优化,例如基于Java的系统有如下参数或行为:

    • 已经通过-Xms-Xmx 指定了内存大小
    • 包含了 -server 参数
    • 系统中未出现 超时 等日志

    换句话说,如果你没有设置内存大小而且出现了大量超时日志,那么你需要在系统中进行GC优化了

    但是有件事要铭记于心:GC优化是你最后的手段

    思考下GC优化的根本原因:Java中创建的对象由垃圾收集器来清理,同时待清理对象的数量和各类GC的执行次数又和创建对象总数量成正比。因此,为了控制GC的执行,首先要做的是 减少创建对象的总数量

    俗话说,”积少成多”。我们需要关注一些小事情,否则”养成气候”之后将难以驾驭。例如:

    • 使用StringBuilder或StringBuffer代替String
    • 尽可能少的输出日志

    然而,有些场景我们也无能为力。我们知道,XML和JSON的解析会消耗大量的内存,就算尽可能少的使用String和日志也作用不大,因为还是会使用大量的临时内存来解析它们,有时甚至是10-100M。但是,又不太可能不使用XML和JSON,只能任由内存被消耗。

    如果在几次参数调整后内存使用情况有所改善,你就可以进行GC优化了。我将GC优化的目的分成两类:

    • 将转移到老年代的对象数量降到最少
    • 减少Full GC的执行时间

    将转移到老年代的对象数量降到最少

    Oracle JVM提供了分代垃圾回收机制(JDK1.7及以上的G1 GC除外)。换句话说,对象创建在Eden区,然后在Survivor的From和To区之间移动,最后存活的对象被转移到老年代。一些大对象在Eden区创建之后被直接转移到老年代。相对新生代,老年代的GC消耗的时间更长。因此,减少从新生代转移到老年代的对象数量可以降低Full GC的频率。

    减少从新生代转移到老年代对象的数量的说法容易造成误解,而且也不可能,但可以通过 调整年轻代的大小 来实现。

    减少Full GC的时间

    和Minor GC相比,Full GC的执行时间长很多。因此,如果执行Full GC的时间过长(超过1s),将导致连接服务的请求超时。

    • 如果通过减少老年代的大小来降低Full GC执行时间,会造成OutOfMemoryError或增加Full GC的次数
    • 如果增大老年代大小以期减少Full GC的执行次数,那么执行时间又会增加

    因此,需要合理的设置 老年代大小

    影响GC性能的参数

    我在 理解 Java GC 中提到过,不要去想 “有人在使用一些GC参数后性能显著提升,为什么我们不使用相同的参数?“,原因是 不同Web应用中对象的大小和生命周期不同

    对于Java GC参数的设置,设置多个参数并不会提高GC的执行速度,恰恰相反,可能会降低执行速度。GC优化的基本原则是:将不同的GC参数应用到2个或多个主机,然后比对结果,最后将性能最优的参数组合推广到其他主机,这点必须铭记于心。

    下表是一些影响GC性能的参数。

    表1: GC优化时需要检查的JVM参数

    分类参数描述
    堆区-Xms启动JVM时的初始堆大小
    -Xmx最大堆内存
    新生代-XX:NewRatio新生代和老年代内存大小比例
    -XX:NewSize新生代大小
    -XX:SurvivorRatioEden和Survivor区的比率

    我经常使用 -Xms、-Xmx、-XX:NewRatio 三个参数来进行GC调优。-Xms、-Xmx 是肯定需要的,-XX:NewRatio 的设置将会显著的影响GC性能。

    有的人可能会问 如何设置Perm区大小? 你可以通过 -XX:PermSize、-XX:MaxPermSize 设置,这个会与Perm区 OutOfMemoryError相关。

    另一个会影响GC性能的是 GC类型,下面是基于JDK1.6可选的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在JDK6,这两个参数必须同时使用

    除G1 GC外,GC类型可以通过第一行的参数来切换。最常见的GC类型是 Servial GC,它针对客户端系统专门进行了优化。

    影响GC性能的参数有很多,但是上面的参数有着最为显著的影响。记住,设置过多的参数并不能保证一定会缩短GC的时间。

    GC优化的步骤

    GC优化过程与一般的性能优化类似,下面是我进行GC优化的步骤。

    1. 监控GC状态

    你需要监控和检查运行中系统的GC状态,监控方式请参考 如何监控Java GC

    1. 根据分析结果决定是否需要GC优化

    在检查GC状态后,你需要分析监控结果,再决定是否进行GC优化。如果分析结果显示GC的时间只不过是0.1-0.3s,就不要浪费时间搞什么GC优化了。然而,如果GC时间有1-3s,甚至超过10s,那GC势在必行

    但是,如果你已经分配了10G Java内存,而且没有办法降低内存大小的话,就没办法进行GC优化了。在GC优化之前,你需要思考下为什么需要分配这么大的内存。如果只是分配了1-2G内存并且发生了OutOfMemoryError,你需要获取heap dump来验证和排查原因。

    1. 设置GC类型和内存大小

    如果你已经决定要进行GC优化,同时,如果你拥有多台机器,一定要检查不同GC参数对不同机器的影响。

    1. 分析结果

    设置GC参数后让程序运行至少24小时,再分析收集到的数据。幸运的话,你可能立马找到对系统最合适的GC参数,否则你需要分析日志,检查内存分配情况,然后再通过调整GC类型和内存大小来找到最佳参数配置。

    1. 如果结果满意,将参数应用到所有机器并停止优化

    在下面的部分,你将看到每一步的具体步骤。

    监控GC状态&分析结果

    检查Web应用GC状态最好的方式是使用 jstat 命令。下面的例子是GC优化之前状态。

    检查YGC和YGCT,YGCT/YGC=0.050s(50ms),这意味着执行Minor GC的平均时间为50ms,你可以不用关注年轻代的GC情况。

    现在,检查FGC和FGCT。FGCT/FGC=19.68s,这意味着GC的平均时间为19.68s。这可能是3次时间都为19.68的GC,也有可能是两次GC总耗时1秒而另一次GC耗时58秒,不过这两种情况都需要进行GC优化。

    虽然可以通过jstat方便的获取GC状态,但分析GC最好的方式是通过-verbosegc 参数产生gc日志。如果GC执行时间符合下面所有条件,那没必要进行GC优化:

    • Minor GC执行很快(少于50ms)
    • Minor GC执行不是很频繁(大概10秒/次)
    • Full GC执行很快(少于1s)
    • Full GC执行不是很频繁(10分钟/次)

    上述的值也不是绝对的,这取决于服务的状态。有的服务Full GC可能只要0.9秒,有的可能长点。因此,是否执行GC优化也要考虑具体的场景。

    设置GC类型和内存大小

    设置GC类型

    Oracle JVM提供了5种GC类型,如果不是JDK7,可以在Parallel GC、Parallel Compacting GC、CMS GC中选择一种,到底选哪种也没什么特殊的规则。

    那么,我到底该选哪种呢? 最推荐的方式是三种都试试。不过可以明确的是CMS GC比两种Parallel GC要快,如果测试CMS GC确实较快,直接使用CMS GC即可。但是,CMS GC也不总是最快的,通常来说,CMS GC执行Full GC的话会快点,不过一旦出现并发模式失败,Parallel GC会更快点。

    并发模式失败

    让我们了解下并发模式失败。

    CMS GC和Parallel GC之间最大的区别是 压缩 任务,压缩任务指的是移除内存碎片。

    在Parallel GC中,在Full GC之后需要执行压缩任务,因此GC时间更长。但是,在Full GC之后,由于可以连续分配内存,内存分配速度会更快。

    CMS GC恰恰相反,Full GC后它不会执行压缩任务。因此,CMS GC执行的更快,不过由于未执行压缩任务,也会产生许多的内存碎片,可能导致无法为大对象分配内存。例如,老年代剩下300M,一些10M的大对象又无法被连续分配,在这种场景下,会发生 “并发模式失败” 警告并执行压缩任务。需要注意,CMS GC的压缩时间比其他Parallel GC时间要长很多,而且可能导致其他问题。更多关于并发模式失败的信息,可以参考Oracle工程师写的 理解CMS GC日志

    最终结论是,每个系统需要其合适的GC类型,你需要为你的系统找到合适的GC类型。如果你运行了6台主机,我建议你每两台设置相同的参数并添加 -verbosegc,然后分析监控结果。

    设置内存大小

    下面展示了内存大小和GC执行次数及时间的关系。

    • 大内存空间
      • 降低了GC执行次数
      • 增加了GC执行时间
    • 小内存空间
      • 增加了GC执行次数
      • 降低了GC执行时间

    内存到底设置小点还是大点并没有标准答案,如果机器资源充足而且Full GC能在1秒以内搞定的话,哪怕内存设置成10G也是可以的。但多数情况下,10G的内存Full GC会消耗10-30s,当然,时间也取决于对象的大小。

    那么,内存大小该怎么设置?一般我推荐500M,不过注意并不是让你把Web应用的内存直接设置成-Xms500m -Xmx500m。在GC优化之前,先检查Full GC之后的内存剩余大小,如果剩下300M,那内存可以设置为1G(300M(默认使用的内存) + 500M(老年代最小内存) + 200M(自由空间)),这意味着老年代至少要设置500M以上。因此,如果你有3台主机,可以将内存分别设置为1G、1.5G、2G来试试效果。

    为了配置内存大小你还需要设置NewRatioNewRatio是新生代和老年代的比率。如果 XX:NewRatio=1,那新生代与老年代大小是1:1,如果堆内存是1G的话,那新生代为500M,老年代也是500M。如果XX:NewRatio=2,那新生代:老年代=1:2。因此,NewRatio值越大,老年代的大小就越大,新生代则越小。

    NewRatio的值将显著影响整个GC的性能。如果新生代太小,很多对象将转移到老年代,导致频繁的Full GC同时也会增加Full GC的时间。

    你可能会想 NewRatio=1 最好,不过事实并非如此,就我见过的案例来说, NewRatio设置为2或3时整个GC状态会更好些

    最快完成GC优化的方式是怎么样的呢?比对不同性能测试的结果是最快的方式。为不同机器设置不同的参数,推荐运行1-2天后再检查数据。但是,进行GC优化时,要确保使用了想同的负载,如:请求的频率和URL都应该一致。不过,由于即使是专业测试人员想控制相同的负载也很苦难,需要花费大量时间准备。因此,相对比较简单的方式是调整参数,然后花费较长的时间来收集结果。

    分析GC优化结果

    在设置GC参数以及-verbosegc参数之后,通过tail命令确保日志被正确的生成。如果参数设置的不正确导致日志没有生成,你就是在浪费时间。如果日志正确的话,持续收集1到2天之后再检查结果。最简单的方式是将日志下载到本地并用HPJMeter来分析。

    分析过程中,关注以下指标。优先级是我个人排列的,决定GC参数最重要的指标是Full GC的执行时间。

    • Full GC 执行时间
    • Minor GC执行时间
    • Full GC 执行间隔
    • Minor GC 执行间隔
    • Entire Full GC 执行时间
    • Entire Minor GC 执行时间
    • Entire GC 执行时间
    • Full GC e执行时间
    • Minor GC 执行时间

    能找到最佳的GC参数是件非常幸运的事情,然而在大多数场合,我们并不能如愿。在进行GC优化时要尽量小心谨慎,如果想一步到位搞定优化,往往会导致OutOfMemoryError 。

    优化例子

    到目前为止,我们都在纸上谈兵,现在让我们看看GC优化的案例。

    案例1

    这个例子是Full GC时间较长。

    通过 jstat -gcutil 获取如下数据:

    S0    S1   E    O     P     YGC YGCT FGC FGCT GCT
    12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993

    Perm区域对于首次GC优化来说并不重要,当前,YGC的值更有价值。

    下面是Minor GC和Full GC 的相关数据:

    GC类型GC执行次数GC执行时间平均时间
    Minor GC542.04737 ms
    Full GC56.9461,389 ms

    37ms对于Minor GC来说不算坏,然而,1.389s的Full GC意味着在GC时系统会频繁超时。

    首先,你需要检查在GC之前内存是如何使用的。通过 jstat –gccapacity 检查内存使用情况,结果如下:

    关键数据如下:

    • New area usage size: 212,992 KB
    • Old area usage size: 1,884,160 KB

    因此,不算Perm区域的话,已分配的总内存是2G,New area:Old area比率为1:9。通过添加 -verbosegc 日志来获取更详细的日志,以下三个选项分别设置在不同机器上,且没有添加其他参数:

    • NewRatio=2
    • NewRatio=3
    • NewRatio=4

    一天之后,获取GC日志。幸运的是,在设置NewRatio之后并为发生Full GC。

    为什么呢?原因是大多数对象创建之后就销毁了,对象不用从新生代移动到老年代。

    在这种状态下,不用改变其他JVM参数,选择一个合适的 NewRatio值即可。那么,我们怎么决定最佳的NewRatio值呢?我们可以分析下不同NewRatio值下的每次Minor GC的平均时间,数据如下:

    • NewRatio=2: 45 ms
    • NewRatio=3: 34 ms
    • NewRatio=4: 30 ms

    现在我们可以下结论了,由于新生代最小,GC时间最短,NewRatio=4是最佳的选择。在使用该选项后,服务器没有再发生Full GC。

    为了说明这个问题,下面是服务运行一段时间后执行jstat –gcutil的结果:

    S0 S1 E O P YGC YGCT FGC FGCT GCT
    8.61 0.00 30.67 24.62 22.38 2424 30.219 0 0.000 30.219

    你可能会认为因为服务器接受的请求少才导致的GC执行频率下降。实际上,虽然Full GC没有执行,但是Minor GC被执行了 2424次。

    案例2

    我们通过公司内部的性能管理系统(APM)发现JVM暂停了相当长的时间(超过8s),因此我们进行了GC优化。我们找到了Full GC时间长的原因并决定进程GC优化。

    首先我们添加了 -verbosegc 参数,下面是结果:

    上图是HPJMeter根据分析结果自动生成的图片,X轴表示JVM启动之后的时间,Y轴表示每次GC的时间。绿色的点是CMS GC,代表Full GC的结果,蓝色的点表示Parallel Scavenge,代表Minor GC结果。

    前面我们说过CMS GC时间是最快的,但是上面的结果显示有些GC花费的时间超过15秒。到底是什么导致了这个结果?记住前面我说过:在压缩任务执行是CMS会更慢点,另外,这个程序内存设置为–Xms1g and –Xmx4g,已分配的内存也达到了4G。

    因此我将GC类型从CMS GC改成了Parallel GC,并将内存改成了2G,NewRatio设置为3。几个小时之后,通过 jstat -gcutil 得到的结果如下:

    S0 S1 E O P YGC YGCT FGC FGCT GCT
    0.00 30.48 3.31 26.54 37.01 226 11.131 4 11.758 22.890

    Full GC时间稍微快了一点,相对4GB时的15秒,现在平均每次为3秒。但是3秒一样比较慢,因此我设计了如下6种场景。

    • Case 1: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=2
    • Case 2: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=3
    • Case 3: -XX:+UseParallelGC -Xms1g -Xmx1g -XX:NewRatio=3
    • Case 4: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=2
    • Case 5: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=3
    • Case 6: -XX:+UseParallelOldGC -Xms1g -Xmx1g -XX:NewRatio=3

      那一个最快呢?结果显示,内存越小,结果越好。下图展示了Case6的结果,这是GC的性能提升最高的。最长的响应时间只有1.7秒,平均时间在1秒之内。

    基于以上结果,我按照Case6调整了GC参数。但是,这导致每天晚上都发生OutOfMemoryError。在这里很难解释具体的原因,简而言之,批数据处理程序导致了内存泄漏,现在相关的问题已经被解决。

    如果GC日志的分析时间很短,然后就将优化结果应用到所有服务器是非常危险的。必须牢记,只有在结合GC日志和应用程序进行分析之后才有可能优化成功。

    我们了解了两个关于GC优化的例子,正如我之前提到的,例子中提到的GC参数,可以设置在相同服务器(CPU、操作系统、JDK版本、运行的服务相同)之上。

    结论

    我并未获取heap dump文件,只是凭借经验进行GC优化,精确地分析内存可以得到更好的优化效果。不过这种分析一般适用于内存使用量相对固定的场景。但是,如果服务严重过载并占用的大量的内存,建议你根据上面的经验进行GC优化。


    欢迎关注陈同学的公众号,一起学习,一起成长

    展开全文
  • 有两台服务器,配置相差不大,运行的代码一样,java的配置的也一样,分配的留啦也一样,但是通过jstat -gc pid 看到的gc次数差别很大,像这样频繁的young gc ,该怎么调试? ``` 192.168.1.138 | CHANGED | rc...
  • 从实际案例聊聊Java应用的GC优化

    千次阅读 2018-03-19 11:56:48
    Java程序性能达不到既定目标,且其他优化手段都已经穷尽时,通常需要调整垃圾回收器来进一步提高性能,称为GC优化。但GC算法复杂,影响GC性能的参数众多,且参数调整又依赖于应用各自的特点,这些因素很大程度上...

    当Java程序性能达不到既定目标,且其他优化手段都已经穷尽时,通常需要调整垃圾回收器来进一步提高性能,称为GC优化。但GC算法复杂,影响GC性能的参数众多,且参数调整又依赖于应用各自的特点,这些因素很大程度上增加了GC优化的难度。即便如此,GC调优也不是无章可循,仍然有一些通用的思考方法。本篇会介绍这些通用的GC优化策略和相关实践案例,主要包括如下内容:

    优化前准备: 简单回顾JVM相关知识、介绍GC优化的一些通用策略。
    优化方法: 介绍调优的一般流程:明确优化目标→优化→跟踪优化结果。
    优化案例: 简述笔者所在团队遇到的GC问题以及优化方案。

    一、优化前的准备

    GC优化需知

    为了更好地理解本篇所介绍的内容,你需要了解如下内容。

    GC相关基础知识,包括但不限于:

    • a) GC工作原理。
    • b) 理解新生代、老年代、晋升等术语含义。
    • c) 可以看懂GC日志。

    GC优化不能解决一切性能问题,它是最后的调优手段。

    如果对第一点中提及的知识点不是很熟悉,可以先阅读小结-JVM基础回顾;如果已经很熟悉,可以跳过该节直接往下阅读。

    JVM基础回顾

    JVM内存结构

    简单介绍一下JVM内存结构和常见的垃圾回收器。

    当代主流虚拟机(Hotspot VM)的垃圾回收都采用“分代回收”的算法。“分代回收”是基于这样一个事实:对象的生命周期不同,所以针对不同生命周期的对象可以采取不同的回收方式,以便提高回收效率。

    Hotspot VM将内存划分为不同的物理区,就是“分代”思想的体现。如图所示,JVM内存主要由新生代、老年代、永久代构成。
    这里写图片描述

    ① 新生代(Young Generation):大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。

    新生代内又分三个区:一个Eden区,两个Survivor区(一般而言),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。

    ② 老年代(Old Generation):在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。

    ③ 永久代(Perm Generation):主要存放元数据,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大。相对于新生代和年老代来说,该区域的划分对垃圾回收影响比较小。

    常见垃圾回收器

    不同的垃圾回收器,适用于不同的场景。常用的垃圾回收器:

    串行(Serial)回收器是单线程的一个回收器,简单、易实现、效率高。
    并行(ParNew)回收器是Serial的多线程版,可以充分的利用CPU资源,减少回收的时间。
    吞吐量优先(Parallel Scavenge)回收器,侧重于吞吐量的控制。
    并发标记清除(CMS,Concurrent Mark Sweep)回收器是一种以获取最短回收停顿时间为目标的回收器,该回收器是基于“标记-清除”算法实现的。

    GC日志

    每一种回收器的日志格式都是由其自身的实现决定的,换而言之,每种回收器的日志格式都可以不一样。但虚拟机设计者为了方便用户阅读,将各个回收器的日志都维持一定的共性。JavaGC日志 中简单介绍了这些共性。

    参数基本策略
    各分区的大小对GC的性能影响很大。如何将各分区调整到合适的大小,分析活跃数据的大小是很好的切入点。

    活跃数据的大小是指,应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是Full GC后堆中老年代占用空间的大小。可以通过GC日志中Full GC之后老年代数据大小得出,比较准确的方法是在程序稳定后,多次获取GC数据,通过取平均值的方式计算活跃数据的大小。活跃数据和各分区之间的比例关系如下(见参考文献1):

    空间倍数
    总大小3-4 倍活跃数据的大小
    新生代1-1.5 活跃数据的大小
    老年代2-3 倍活跃数据的大小
    永久代1.2-1.5 倍Full GC后的永久代空间占用

    例如,根据GC日志获得老年代的活跃数据大小为300M,那么各分区大小可以设为:

    总堆:1200MB = 300MB × 4
    新生代:450MB = 300MB × 1.5
    老年代: 750MB = 1200MB - 450MB

    这部分设置仅仅是堆大小的初始值,后面的优化中,可能会调整这些值,具体情况取决于应用程序的特性和需求。

    二、优化步骤

    GC优化一般步骤可以概括为:确定目标、优化参数、验收结果。

    确定目标

    明确应用程序的系统需求是性能优化的基础,系统的需求是指应用程序运行时某方面的要求,譬如:

    • 高可用,可用性达到几个9。
    • 低延迟,请求必须多少毫秒内完成响应。
    • 高吞吐,每秒完成多少次事务。

    明确系统需求之所以重要,是因为上述性能指标间可能冲突。比如通常情况下,缩小延迟的代价是降低吞吐量或者消耗更 多的内存或者两者同时发生。

    由于笔者所在团队主要关注高可用和低延迟两项指标,所以接下来分析,如何量化GC时间和频率对于响应时间和可用性的影响。通过这个量化指标,可以计算出当前GC情况对服务的影响,也能评估出GC优化后对响应时间的收益,这两点对于低延迟服务很重要。

    举例:假设单位时间T内发生一次持续25ms的GC,接口平均响应时间为50ms,且请求均匀到达,根据下图所示:
    这里写图片描述

    那么有(50ms+25ms)/T比例的请求会受GC影响,其中GC前的50ms内到达的请求都会增加25ms,GC期间的25ms内到达的请求,会增加0-25ms不等,如果时间T内发生N次GC,受GC影响请求占比=(接口响应时间+GC时间)×N/T 。可见无论降低单次GC时间还是降低GC次数N都可以有效减少GC对响应时间的影响。

    优化

    通过收集GC信息,结合系统需求,确定优化方案,例如选用合适的GC回收器、重新设置内存比例、调整JVM参数等。

    进行调整后,将不同的优化方案分别应用到多台机器上,然后比较这些机器上GC的性能差异,有针对性的做出选择,再通过不断的试验和观察,找到最合适的参数。

    验收优化结果

    将修改应用到所有服务器,判断优化结果是否符合预期,总结相关经验。

    接下来,我们通过三个案例来实践以上的优化流程和基本原则(本文中三个案例使用的垃圾回收器均为ParNew+CMS,CMS失败时Serial Old替补)。

    三、GC优化案例

    案例一 Major GC和Minor GC频繁

    确定目标

    服务情况:Minor GC每分钟100次 ,Major GC每4分钟一次,单次Minor GC耗时25ms,单次Major GC耗时200ms,接口响应时间50ms。

    由于这个服务要求低延时高可用,结合上文中提到的GC对服务响应时间的影响,计算可知由于Minor GC的发生,12.5%的请求响应时间会增加,其中8.3%的请求响应时间会增加25ms,可见当前GC情况对响应时间影响较大。

    (50ms+25ms)× 100次/60000ms = 12.5%,50ms × 100次/60000ms = 8.3% 。

    优化目标:降低TP99、TP90时间。

    优化

    首先优化Minor GC频繁问题。通常情况下,由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此可以通过增大新生代空间来降低Minor GC的频率。例如在相同的内存分配率的前提下,新生代中的Eden区增加一倍,Minor GC的次数就会减少一半。

    这时很多人有这样的疑问,扩容Eden区虽然可以减少Minor GC的次数,但会增加单次Minor GC时间么?根据上面公式,如果单次Minor GC时间也增加,很难保证最后的优化效果。我们结合下面情况来分析,单次Minor GC时间主要受哪些因素影响?是否和新生代大小存在线性关系?
    首先,单次Minor GC时间由以下两部分组成:T1(扫描新生代)和 T2(复制存活对象到Survivor区)如下图。(注:这里为了简化问题,我们认为T1只扫描新生代判断对象是否存活的时间,其实该阶段还需要扫描部分老年代,后面案例中有详细描述。)
    这里写图片描述

    • 扩容前:新生代容量为R ,假设对象A的存活时间为750ms,Minor GC间隔500ms,那么本次Minor GC时间= T1(扫描新生代R)+T2(复制对象A到S)。

    • 扩容后:新生代容量为2R ,对象A的生命周期为750ms,那么Minor GC间隔增加为1000ms,此时Minor GC对象A已不再存活,不需要把它复制到Survivor区,那么本次GC时间 = 2 × T1(扫描新生代R),没有T2复制时间。

    可见,扩容后,Minor GC时增加了T1(扫描时间),但省去T2(复制对象)的时间,更重要的是对于虚拟机来说,复制对象的成本要远高于扫描成本,所以,单次Minor GC时间更多取决于GC后存活对象的数量,而非Eden区的大小。因此如果堆中短期对象很多,那么扩容新生代,单次Minor GC时间不会显著增加。下面需要确认下服务中对象的生命周期分布情况:

    这里写图片描述

    通过上图GC日志中两处红色框标记内容可知:

    1. new threshold = 2(动态年龄判断,对象的晋升年龄阈值为2),对象仅经历2次Minor GC后就晋升到老年代,这样老年代会迅速被填满,直接导致了频繁的Major GC。
      Major GC后老年代使用空间为300M+,意味着此时绝大多数(86% = 2G/2.3G)的对象已经不再存活,也就是说生命周期长的对象占比很小。

    2. 由此可见,服务中存在大量短期临时对象,扩容新生代空间后,Minor GC频率降低,对象在新生代得到充分回收,只有生命周期长的对象才进入老年代。这样老年代增速变慢,Major GC频率自然也会降低。

    优化结果
    通过扩容新生代为为原来的三倍,单次Minor GC时间增加小于5ms,频率下降了60%,服务响应时间TP90,TP99都下降了10ms+,服务可用性得到提升。

    调整前:
    这里写图片描述

    调整后:
    这里写图片描述

    小结

    如何选择各分区大小应该依赖应用程序中对象生命周期的分布情况:如果应用存在大量的短期对象,应该选择较大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大。

    更多思考

    关于上文中提到晋升年龄阈值为2,很多同学有疑问,为什么设置了MaxTenuringThreshold=15,对象仍然仅经历2次Minor GC,就晋升到老年代?这里涉及到“动态年龄计算”的概念。

    动态年龄计算:Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值。在本案例中,调优前:Survivor区 = 64M,desired survivor = 32M,此时Survivor区中age<=2的对象累计大小为41M,41M大于32M,所以晋升年龄阈值被设置为2,下次Minor GC时将年龄超过2的对象被晋升到老年代。

    JVM引入动态年龄计算,主要基于如下两点考虑:

    如果固定按照MaxTenuringThreshold设定的阈值作为晋升条件:

    • MaxTenuringThreshold设置的过大,原本应该晋升的对象一直停留在Survivor区,直到Survivor区溢出,一旦溢出发生,Eden+Svuvivor中对象将不再依据年龄全部提升到老年代,这样对象老化的机制就失效了。
    • MaxTenuringThreshold设置的过小,“过早晋升”即对象不能在新生代充分被回收,大量短期对象被晋升到老年代,老年代空间迅速增长,引起频繁的Major GC。分代回收失去了意义,严重影响GC性能。

    相同应用在不同时间的表现不同:特殊任务的执行或者流量成分的变化,都会导致对象的生命周期分布发生波动,那么固定的阈值设定,因为无法动态适应变化,会造成和上面相同的问题。

    总结来说,为了更好的适应不同程序的内存情况,虚拟机并不总是要求对象年龄必须达到Maxtenuringthreshhold再晋级老年代。

    案例二 请求高峰期发生GC,导致服务可用性下降

    确定目标

    GC日志显示,高峰期CMS在重标记(Remark)阶段耗时1.39s。Remark阶段是Stop-The-World(以下简称为STW)的,即在执行垃圾回收时,Java应用程序中除了垃圾回收器线程之外其他所有线程都被挂起,意味着在此期间,用户正常工作的线程全部被暂停下来,这是低延时服务不能接受的。本次优化目标是降低Remark时间。
    这里写图片描述

    优化
    解决问题前,先回顾一下CMS的四个主要阶段,以及各个阶段的工作内容。下图展示了CMS各个阶段可以标记的对象,用不同颜色区分。

    1. Init-mark初始标记(STW) ,该阶段进行可达性分析,标记GC ROOT能直接关联到的对象,所以很快。
    2. Concurrent-mark并发标记,由前阶段标记过的绿色对象出发,所有可到达的对象都在本阶段中标记。
    3. Remark重标记(STW) ,暂停所有用户线程,重新扫描堆中的对象,进行可达性分析,标记活着的对象。因为并发标记阶段是和用户线程并发执行的过程,所以该过程中可能有用户线程修改某些活跃对象的字段,指向了一个未标记过的对象,如下图中红色对象在并发标记开始时不可达,但是并行期间引用发生变化,变为对象可达,这个阶段需要重新标记出此类对象,防止在下一阶段被清理掉,这个过程也是需要STW的。特别需要注意一点,这个阶段是以新生代中对象为根来判断对象是否存活的。
    4. 并发清理,进行并发的垃圾清理。

    这里写图片描述

    可见,Remark阶段主要是通过扫描堆来判断对象是否存活。那么准确判断对象是否存活,需要扫描哪些对象?CMS对老年代做回收,Remark阶段仅扫描老年代是否可行?结论是不可行,原因如下:
    这里写图片描述

    如果仅扫描老年代中对象,即以老年代中对象为根,判断对象是否存在引用,上图中,对象A因为引用存在新生代中,它在Remark阶段就不会被修正标记为可达,GC时会被错误回收。
    新生代对象持有老年代中对象的引用,这种情况称为“跨代引用”。因它的存在,Remark阶段必须扫描整个堆来判断对象是否存活,包括图中灰色的不可达对象。

    灰色对象已经不可达,但仍然需要扫描的原因:新生代GC和老年代的GC是各自分开独立进行的,只有Minor GC时才会使用根搜索算法,标记新生代对象是否可达,也就是说虽然一些对象已经不可达,但在Minor GC发生前不会被标记为不可达,CMS也无法辨认哪些对象存活,只能全堆扫描(新生代+老年代)。由此可见堆中对象的数目影响了Remark阶段耗时。
    分析GC日志可以得出同样的规律,Remark耗时>500ms时,新生代使用率都在75%以上。这样降低Remark阶段耗时问题转换成如何减少新生代对象数量。

    新生代中对象的特点是“朝生夕灭”,这样如果Remark前执行一次Minor GC,大部分对象就会被回收。CMS就采用了这样的方式,在Remark前增加了一个可中断的并发预清理(CMS-concurrent-abortable-preclean),该阶段主要工作仍然是并发标记对象是否存活,只是这个过程可被中断。此阶段在Eden区使用超过2M时启动,当然2M是默认的阈值,可以通过参数修改。如果此阶段执行时等到了Minor GC,那么上述灰色对象将被回收,Reamark阶段需要扫描的对象就少了。

    除此之外CMS为了避免这个阶段没有等到Minor GC而陷入无限等待,提供了参数CMSMaxAbortablePrecleanTime ,默认为5s,含义是如果可中断的预清理执行超过5s,不管发没发生Minor GC,都会中止此阶段,进入Remark。
    根据GC日志红色标记2处显示,可中断的并发预清理执行了5.35s,超过了设置的5s被中断,期间没有等到Minor GC ,所以Remark时新生代中仍然有很多对象。
    对于这种情况,CMS提供CMSScavengeBeforeRemark参数,用来保证Remark前强制进行一次Minor GC。

    优化结果

    经过增加CMSScavengeBeforeRemark参数,单次执行时间>200ms的GC停顿消失,从监控上观察,GCtime和业务波动保持一致,不再有明显的毛刺。
    这里写图片描述

    小结

    通过案例分析了解到,由于跨代引用的存在,CMS在Remark阶段必须扫描整个堆,同时为了避免扫描时新生代有很多对象,增加了可中断的预清理阶段用来等待Minor GC的发生。只是该阶段有时间限制,如果超时等不到Minor GC,Remark时新生代仍然有很多对象,我们的调优策略是,通过参数强制Remark前进行一次Minor GC,从而降低Remark阶段的时间。

    更多思考

    案例中只涉及老年代GC,其实新生代GC存在同样的问题,即老年代可能持有新生代对象引用,所以Minor GC时也必须扫描老年代。

    JVM是如何避免Minor GC时扫描全堆的?

    经过统计信息显示,老年代持有新生代对象引用的情况不足1%,根据这一特性JVM引入了卡表(card table)来实现这一目的。如下图所示:

    这里写图片描述

    卡表的具体策略是将老年代的空间分成大小为512B的若干张卡(card)。卡表本身是单字节数组,数组中的每个元素对应着一张卡,当发生老年代引用新生代时,虚拟机将该卡对应的卡表元素设置为适当的值。如上图所示,卡表3被标记为脏(卡表还有另外的作用,标识并发标记阶段哪些块被修改过),之后Minor GC时通过扫描卡表就可以很快的识别哪些卡中存在老年代指向新生代的引用。这样虚拟机通过空间换时间的方式,避免了全堆扫描。

    总结来说,CMS的设计聚焦在获取最短的时延,为此它“不遗余力”地做了很多工作,包括尽量让应用程序和GC线程并发、增加可中断的并发预清理阶段、引入卡表等,虽然这些操作牺牲了一定吞吐量但获得了更短的回收停顿时间。

    案例三 发生Stop-The-World的GC

    确定目标

    GC日志如下图(在GC日志中,Full GC是用来说明这次垃圾回收的停顿类型,代表STW类型的GC,并不特指老年代GC),根据GC日志可知本次Full GC耗时1.23s。这个在线服务同样要求低时延高可用。本次优化目标是降低单次STW回收停顿时间,提高可用性。

    这里写图片描述

    优化

    首先,什么时候可能会触发STW的Full GC呢?

    1. Perm空间不足;
    2. CMS GC时出现promotion failed和concurrent mode failure(concurrent mode failure发生的原因一般是CMS正在进行,但是由于老年代空间不足,需要尽快回收老年代里面的不再被使用的对象,这时停止所有的线程,同时终止CMS,直接进行Serial Old GC);
    3. 统计得到的Young GC晋升到老年代的平均大小大于老年代的剩余空间;
    4. 主动触发Full GC(执行jmap -histo:live [pid])来避免碎片问题。

    然后,我们来逐一分析一下:

    • 排除原因2:如果是原因2中两种情况,日志中会有特殊标识,目前没有。
    • 排除原因3:根据GC日志,当时老年代使用量仅为20%,也不存在大于2G的大对象产生。
    • 排除原因4:因为当时没有相关命令执行。
    • 锁定原因1:根据日志发现Full GC后,Perm区变大了,推断是由于永久代空间不足容量扩展导致的。

    找到原因后解决方法有两种:

    1. 通过把-XX:PermSize参数和-XX:MaxPermSize设置成一样,强制虚拟机在启动的时候就把永久代的容量固定下来,避免运行时自动扩容。
    2. CMS默认情况下不会回收Perm区,通过参数CMSPermGenSweepingEnabled、CMSClassUnloadingEnabled ,可以让CMS在Perm区容量不足时对其回收。

    由于该服务没有生成大量动态类,回收Perm区收益不大,所以我们采用方案1,启动时将Perm区大小固定,避免进行动态扩容。

    优化结果

    调整参数后,服务不再有Perm区扩容导致的STW GC发生。

    小结

    对于性能要求很高的服务,建议将MaxPermSize和MinPermSize设置成一致(JDK8开始,Perm区完全消失,转而使用元空间。而元空间是直接存在内存中,不在JVM中),Xms和Xmx也设置为相同,这样可以减少内存自动扩容和收缩带来的性能损失。虚拟机启动的时候就会把参数中所设定的内存全部化为私有,即使扩容前有一部分内存不会被用户代码用到,这部分内存在虚拟机中被标识为虚拟内存,也不会交给其他进程使用。

    四、总结

    结合上述GC优化案例做个总结:

    1. 首先再次声明,在进行GC优化之前,需要确认项目的架构和代码等已经没有优化空间。我们不能指望一个系统架构有缺陷或者代码层次优化没有穷尽的应用,通过GC优化令其性能达到一个质的飞跃。
    2. 其次,通过上述分析,可以看出虚拟机内部已有很多优化来保证应用的稳定运行,所以不要为了调优而调优,不当的调优可能适得其反。
    3. 最后,GC优化是一个系统而复杂的工作,没有万能的调优策略可以满足所有的性能指标。GC优化必须建立在我们深入理解各种垃圾回收器的基础上,才能有事半功倍的效果。

    4. 本文中案例均来北京业务安全中心(也称风控)对接服务的实践经验。同时感谢风控的小伙伴们,是他们专业负责的审阅,才让这篇文章更加完善。对于本文中涉及到的内容,欢迎大家指正和补充。

    作者简介
    录录,2016年加入美团点评,主要负责北京业务安全中心对接服务的后台研发工作。

    参考文献
    Scott O. Java Performance:The Definitive Guide. O’Reilly, 2014.
    周志明,深入理解Java虚拟机[M],机械工业出版社,2013.
    CMS垃圾回收机制.

    转载来源: https://tech.meituan.com/jvm_optimize.html

    展开全文
  • Java服务器宕机解决方法论

    万次阅读 多人点赞 2020-02-29 03:27:17
    一般我们认为向服务器的请求都没有响应或者响应非常缓慢的情况都称为宕机. 表面的前端崩溃并不是宕机 1.2 分类 进程闪退 内部崩溃 外部终止 线程锁死或者无限等待 内存溢出 下面分别进行详解 2 进程闪退 2.1 ...
  • System.gc()是用Java,C#和许多其他流行的高级编程语言提供的API。当它被调用时,它将尽最大努力从内存中清除垃圾(即未被引用的对象)。名词解释:GC,Garbage Collection,垃圾回收,下文会经常使用。二、谁可以...
  • Java虚拟机GC

    千次阅读 2018-04-14 19:55:10
    说一下Java的垃圾回收机制 它使得 Java 程序员在编写程序的时候不再需要考虑内存管理。垃圾回收器通常是作为一个单独... 程序员可以手动执行System.gc(), 通知 GC 运行, 但是 Java 语言规范并不保证 GC 一定会执...
  • java超出gc开销限制 这篇文章是我们原来的GC开销超出问题模式的延续。 正确的Java堆分析对于消除O​​utOfMemoryError:GC开销问题至关重要。 如果您不熟悉此Java HotSpot 1.6错误,建议您首先阅读有关此主题的第...
  • Minecraft-Java-Shenandoah-GC:使用出色的并行实时垃圾收集器运行Minecraft Java服务器,以减少延迟
  • Java VisualVM安装Visual GC插件

    千次阅读 2019-08-14 12:27:41
    如果要查看jvm gc的具体信息,比如heap堆中Eden,Old,则要装个插件visualgcjava9及以后,就停止使用java VisualVM了,改用Graal VisualVM了。 开始安装: 找到jdk目录的bin下面,打开jvisualvm.exe,然后 ...
  • 文档:【Java内存溢出排查】测试环境服务器挂... 链接:http://note.youdao.com/noteshare?id=783e7ec89950f4167867ef3ef33470b6&sub=48AEFC6FDECB4C60869FAA5FABF57AB0 通过以下命令信息可以确定是内存溢出...
  • 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员...
  • Java的性能优化,JVM GC(垃圾回收机制)在学习Java GC 之前,我们需要记住一个单词:stop-the-world 。它会在任何一种GC算法中发生。stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行。当stop-the-...
  • 常见Java GC Root

    千次阅读 2019-01-16 22:42:42
    Java 进行GC的时,会从GC root进行可达性判断,常见的GC Root有: 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root 处于激活状态的线程 栈中的对象...
  • java.lang.OutOfMemoryError异常解决方法原因: 常见的有以下几种: 1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; 3.代码中存在死...
  • JAVAGC线程

    千次阅读 2017-09-13 09:51:22
    垃圾回收机制GC知识再总结兼谈如何用好GC
  • Java GC系列:Java垃圾回收详解

    千次阅读 2020-12-24 10:55:59
    Java的内存分配与回收全部由...下面四篇教程是了解Java 垃圾回收(GC)的基础:这篇教程是系列第一部分。首先会解释基本的术语,比如JDK、JVM、JRE和HotSpotVM。接着会介绍JVM结构和Java 堆内存结构。理解这些基础对...
  • 1、通过阅读GC日志,我们可以了解Java虛拟机内存分配与回收策略。 内存分配与垃圾回收的参数列表: (1)-XX:+PrintGC 输出GC日志(这个只会显示总的GC堆的变化)。类似: -verbose:gc (2)-XX:+PrintGCDetails 输出...
  • Java JVM堆内存的GC模型以及分代机制,包括堆空间GC分区,对象晋升,GC触发策略等。
  • Java内存模型与GC

    千次阅读 2016-07-07 17:23:23
    Java内存模型 JVM Spec中的Runtime Data Area分为5个区域:pc register(PC寄存器)、java stack(JVM方法栈)、native stack(本地方法栈)、java heap、method area。前三个和大多数语言类似比较容易理解,java Heap...
  • java.lang.OutOfMemoryError: GC overhead limit exceeded原因及解决方法 问题复现:11-23日生产推送数据时,发生该错误,没有其他操作. 解释STW:jvm在执行垃圾回收线程时,其他线程都会被迫停止,只有垃圾收集线程...
  • 前言,线上的是一台java服务,启动参数如下所示: -Xmx5g -Xms5g -Xmn3g -Xss256k -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:...
  • Java中9种常见的CMS GC问题分析与解决

    千次阅读 多人点赞 2020-11-12 19:58:21
    总第422篇2020年 第46篇目前,互联网上 JavaGC 资料要么是主要讲解理论,要么就是针对单一场景的 GC 问题进行了剖析,对整个体系总结的资料少之又少。前车之鉴,后事之师...
  • 查看了日志,出现了大量的java.lang.OutOfMemoryError: GC overhead limit exceeded错误。 oracle官方给出了这个错误产生的原因和解决方法: Exception in thread thread_name: java.lang.OutOfMemoryError: GC O.....
  • [Java基础]--JVM gc参数设置与分析

    千次阅读 2018-05-19 17:16:41
    感谢原文链接:http://xstarcd.github.io/wiki/Java/JVM_GC.htmlJVM gc参数设置与分析原文:http://hi.baidu.com/i1see1you/item/295c1dc81f91ab55bdef69e5gc日志分析工具: ...
  • Java 8 GC 调优】Ergonomics

    千次阅读 2020-01-14 09:42:18
    Ergonomics 是 “JVM” 和 “GC调优” 提高应用程序性能的过程(如,基于行为的调优)。JVM 根据所运行的平台提供了 GC、堆大小、运行时编译器 等方面的默认选择。这些选择符合不同类型应用程序的需求,可减少命令行...
  • CPU飙高,频繁GC,怎么排查?

    千次阅读 2021-02-26 16:28:05
    作者:爱宝贝丶来源:my.oschina.net/zhangxufeng/blog/3017521处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题。当然,这些问题的最终导致的直观现象就是系统运行缓慢,...
  • 关于Java VisualVm添加VisualGC插件显示不支持的问题的解决办法 1.修改远程服务器java设置 找到jdk下的/jre/lib/security/java.policy文件,打开此文件 在文件末尾的 }; 前添加permission java.security.All...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 94,615
精华内容 37,846
关键字:

java服务器解决gc

java 订阅