美文网首页
减少长时间的GC停顿

减少长时间的GC停顿

作者: 专职掏大粪 | 来源:发表于2020-08-26 11:41 被阅读0次

    长GC暂停对于应用程序是不希望的。它会影响您的SLA;它导致不良的客户体验,并严重损害关键任务应用程序。因此,在本文中,我提出了可能导致长时间GC暂停的关键原因以及解决这些问题的潜在解决方案

    1.高对象创造率

    如果您的应用程序的对象创建率很高,因此,垃圾回收率也将非常高。高垃圾收集率也会增加GC暂停时间。因此,优化应用程序以创建较少数量的对象是减少长GC暂停的有效策略。这可能是比较耗时,但值得100%进行。为了优化应用程序中的对象创建速度,您可以考虑使用Java Profiler(如 JProfiler, YourKit,JVisualVM ...)。这些分析器将报告

    • 创建的对象是什么?
    • 这些对象的创建速率是多少?
    • 它们在内存中占用多少空间?
    • 谁在创造它们?
      一直尝试优化占用最大内存量的对象

    小提示:如何计算对象创建率?

    2.年轻的空间过小

    当年轻代太小时,对象将被过早地提升为老代。从老年代收集垃圾要比从年轻代收集垃圾花费更多的时间。因此,增加年轻代的大小可以减少长时间的GC暂停。可以通过设置两个JVM参数中的任何一个来增加年轻代

    • -Xmn:指定年轻代的大小

    • -XX:NewRatio:指定老年代和年轻代的比例。例如,设置-XX:NewRatio=3,表示老一代和年轻一代的比例是3:1。也就是说,年轻代将占整个堆的四分之一。例如,如果堆大小是2 GB,那么年轻一代的大小将是0.5 GB

    3. GC算法的选择

    GC算法的选择对GC暂停时间有很大影响。除非您是GC专家或者打算成为一个专家或者团队中的某人是GC专家,否则您可以调整GC设置以获得最佳的GC暂停时间。假设您不具备GC专业知识,那么我建议您使用G1 GC算法,因为它具有 自动调整功能。在G1 GC中,您可以使用系统属性“ -XX:MaxGCPauseMillis”设置GC暂停时间目标。例:

     -XX:MaxGCPauseMillis = 200 
    

    根据上面的示例,最大GC暂停时间设置为200毫秒。这是一个软目标,JVM将尽力实现这一目标。如果您已经在使用G1 GC算法,并且仍然继续经历高暂停时间,请参考本文。

    4. 内存交换

    有时由于内存不足(RAM),操作系统可能正在从内存中交换应用程序.Swapping非常昂贵,因为它需要磁盘访问,这比物理内存访问要慢得多.交换过程时,GC将花费很长时间才能完成。

    下面是从StackOverflow获得的脚本(感谢作者)-执行该脚本 将显示所有正在交换的进程。请确保您的进程没有被交换

                #!/bin/bash 
                # Get current swap usage for all running processes
                # Erik Ljungstrom 27/05/2011 
                # Modified by Mikko Rantalainen 2012-08-09 
                # Pipe the output to "sort -nk3" to get sorted output
                # Modified by Marc Methot 2014-09-18 
                # removed the need for sudo 
    
                SUM=0 
                OVERALL=0 
                for DIR in `find /proc/ -maxdepth 1 -type d -regex "^/proc/[0-9]+"`
                do
                    PID=`echo $DIR | cut -d / -f 3`
                    PROGNAME=`ps -p $PID -o comm --no-headers`
                                
    
                    for SWAP in `grep VmSwap $DIR/status 2>/dev/null | awk '{ print $2 }'`
                    do
                        let SUM=$SUM+$SWAP
                    done
                    if (( $SUM > 0 )); then
                        echo "PID=$PID swapped $SUM KB ($PROGNAME)"
                    fi
                    let OVERALL=$OVERALL+$SUM
                    SUM=0
                done
                echo "Overall swap used: $OVERALL KB" 
                            
    

    如果发现进程正在交换,请执行以下操作之一:

    a。向服务器分配更多RAM

    b。减少服务器上运行的进程数,以便它可以释放内存(RAM)。

    C。减小应用程序的堆大小(我不建议这样做,因为它可能导致其他副作用).

    5. GC Threads过少

    对于GC日志中报告的每个GC事件,将打印user,sys和real time。例:

     [Times: user=25.56 sys=0.35, real=20.48 secs]
    

    (如果在GC事件中您始终注意到“real time”并不比“user”时间显着少,则可能表明GC线程不足。考虑增加GC线程数。假设“user”时间为25秒,并且您已将GC线程数配置为5,那么“real time”应接近5秒(因为25秒/ 5个线程= 5秒)。

    警告:添加过多的GC线程将消耗大量CPU,并会占用应用程序的资源。因此,您需要在增加GC线程数之前进行彻底的测试

    6.后台IO流量

    如果文件系统的I / O活动繁重(即发生大量读取和写入操作),也会导致长时间的GC暂停。这种繁重的文件系统I / O活动可能不是由您的应用程序引起的。可能是由于同一服务器上正在运行的另一个进程引起的,仍然可能导致您的应用程序长时间处于GC暂停状态(https://engineering.linkedin.com/blog/2016/02/eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic)

    当I / O繁忙时,您会发现“real time”时间要比“user”时间长得多。例:

     [时间:用户= 25.56 sys = 0.35,实际= 20.48秒]
    

    当发生这种模式时,可以使用以下解决方案:

    a。如果您的应用程序引起了很高的I / O活动,请对其进行优化。

    b。消除导致服务器上大量I / O活动的进程

    C。将您的应用程序移到I / O活动较少的其他服务器上

    Tit-bit: 如何监控io?
    您可以在Unix中使用sar(系统活动报告)监视I/O活动。例子:

     sar -d -p 1
    
    

    上面的命令报告每1秒对设备所做的读/秒和写/秒。有关“sar”命令的更多细节,请参阅本教程。

    7. System.gc() 调用

    1.您自己的应用程序开发人员可能正在显式调用System.gc()方法。

    2.可能是第三方库,框架,有时甚至是您使用的应用程序服务器也可能正在调用System.gc()方法。

    3.可以通过使用JMX从外部工具(如VisualVM)触发

    4.如果您的应用程序正在使用RMI,则RMI会定期调用System.gc()。可以使用以下系统属性来配置此间隔:

    -Dsun.rmi.dgc.server.gcInterval = n

    -Dsun.rmi.dgc.client.gcInterval = n

    评估是否绝对需要显式调用System.gc()。如果不需要,请删除它。另一方面,可以通过传递JVM参数'-XX:+ DisableExplicitGC'来强制禁用System.gc()调用
    Tit-bit:如何知道是否显式调用了System.gc()调用?

    image.png

    8. 分配过大的堆空间

    大堆大小(-Xmx)也可能导致长时间的GC暂停。如果堆大小很大,那么堆中将堆积更多的垃圾。当触发Full GC来清除堆中所有累积的垃圾时,将需要很长时间才能完成。逻辑很简单:如果您的小罐子里装满了垃圾,将可以轻松快捷地进行处理。另一方面,如果您有卡车装满的垃圾,将需要更多的时间来处理它们。

    假设您的JVM堆大小为18GB,然后考虑拥有三个6 GB JVM实例,而不是一个18GB JVM。较小的堆大小具有降低长时间GC暂停的巨大潜力。

    注意:上述所有策略均应在经过全面测试和分析后才能投入生产。所有策略可能不适用于您的应用程序。这些策略使用不当会导致负面结果

    9.Workload distribution

    即使有多个GC线程,有时工作负载也会在GC工作线程之间平均分配。有很多原因导致GC工作负载可能无法平均分配到GC线程中。例如:

    a。当前无法并行扫描大型线性数据结构。

    b。某些事件仅触发单个线程收集器(例如,CMS收集中出现“并发模式故障”时)

    如果碰巧使用CMS(并发标记和清除算法),则可以考虑传递 -XX:+ CMSScavengeBeforeRemark参数。这样可以在GC工作线程之间创建更加平衡的工作负载

    CMSScavengeBeforeRemark:CMS并发标记阶段与用户线程并发进行,此阶段会产生已经被标记了的对象又发生变化的情况,若打开此开关,可在一定程度上降低CMS重新标记阶段对上述“又发生变化”对象的扫描时间,当然,“清除尝试”也会消耗一些时间
    注,开启此开关并不会保证在标记阶段前一定会进行清除操作

    翻译自
    https://gceasy.io/gc-recommendations/long-pause-solution.jsp

    相关文章

      网友评论

          本文标题:减少长时间的GC停顿

          本文链接:https://www.haomeiwen.com/subject/nhxzjktx.html