长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()调用?
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
网友评论