JVM的GC停顿时间过长该怎么处理?

2023-11-09 02:59

本文主要是介绍JVM的GC停顿时间过长该怎么处理?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文在这里: JVM的GC停顿时间过长该怎么处理?
扫一扫加关注【爪哇优太儿】
应用运行过程中是不希望出现长时间的GC停顿的,因为这会影响服务的可用性,导致用户体验变差,甚至会严重损害一些关键的应用程序。本文将会列出可能导致GC停顿时间长的一些原因和解决方案。

1.对象创建的速度过高

如果应用创建对象的速度非常高,随之而来的就是GC频率也会变快,然后会导致GC的停顿时间变长。所以说,优化代码以降低对象的创建速率是降低GC停顿时间最有效的方法。这可能是一件非常耗时的事情,但是却非常值得去做。可以使用JProfiler, YourKit, JVisualVM这样的性能监控工具来帮助优化对象的创建速度,这些工具会分析出:应用到底创建了哪些对象?对象创建的速度是多少?这些对象占用了多少内存空间?是谁创建的这些对象?所谓擒贼先擒王,因此首先要考虑优化那些占用内存最多的对象。
tip1:如何知道对象的创建速度?把GC日志上传到gceasy.io,这个工具会告诉你对象的创建速度,下图中‘Object Stats’里面的 ‘Avg creation rate’ 就是对象的平均创建速度。要让这个值尽可能的小。
在这里插入图片描述

2.Young区过小

如果Young过小,对象就会过早的晋升到Old区,Old区的垃圾回收一般比Young区会花费更多的时间,因此,可以通过增大Young区来有效的降低长时间GC停顿。可以用下面两个JVM参数来设置Young区的大小:
-Xmn: 设置Young区所占的字节数
-XX:NewRatio: 设置Old区和Young区的比例,比如说,-XX:NewRatio=3也就是说Old区和Young区的比例是3:1,Young区占整个堆的1/4,如果堆是2G,那么Young区就是0.5G。

3.选择合适的GC算法

GC算法是影响GC停顿时间的一个非常重要的因素,除非你是个GC方面的专家或者你的团队中有这方面的专家可以调优GC的设置达到最优的停顿时间,否则我建议选择使用G1收集器,因为G1是自动调优的,你只需要设置一个停顿时间的目标就可以了,比如: -XX:MaxGCPauseMillis=200。这个例子设置了最大停顿时间的目标是200ms,JVM会尽最大努力来满足这个目标。如果你已经使用了G1但是还是出现了长时间的GC停顿,那么请继续阅读本文。

4.进程被交换(Swap)出内存

有时候由于系统内存不足,操作系统会把你的应用从内存中交换出去。Swap是非常耗时的,因为需要访问磁盘,相对于访问物理内存来说要慢得多的多。我认为生产环境下的应用是不应该被Swap出内存的。当发生进程Swap的时候,GC停顿时间也会变长。
下面是从stackoverflow上引用的一个脚本,它能够列出被Swap出内存的进程,要确保你的应用没有被Swap出内存。


#!/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 sudoSUM=0
OVERALL=0
for DIR in `find /proc/ -maxdepth 1 -type d -regex "^/proc/[0-9]+"`
doPID=`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 }'`dolet SUM=$SUM+$SWAPdoneif (( $SUM > 0 )); thenecho "PID=$PID swapped $SUM KB ($PROGNAME)"filet OVERALL=$OVERALL+$SUMSUM=0
done
echo "Overall swap used: $OVERALL KB"

如果很不幸你的应用被Swap了,你需要:
a:给机器增加内存
b:减少机器上运行的进程数,以释放更多的内存
c:减少应用分配的内存(不推荐,可能会引起其他问题)

5.GC线程数过少

GC日志中的每一个GC事件都会打印user、sys、real time,比如:

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

这几个时间的区别可以查看前面文章:GC日志中sys时间比user时间长该如何处理?GC日志中real时间比user+sys时间长该如何处理?如果GC日志中,real time并不是明显比user time小,这就说明GC线程数是不够的,这就需要增加GC线程了。假如说,user time是25秒,GC线程数是5,那么real time大概是5左右才是正常的(25/5=5)。
注意:GC线程过多会占用大量的系统CPU,从而会影响应用能使用的CPU资源,因此增加GC线程之前一定要做好测试才可以。

6.IO负载重

如果系统的IO负载很重(大量的文件读写)也会导致GC停顿时间过长。这些IO读写不一定是你的应用引起的,可能是机器上其他的进程导致的,但是这仍然会导致你的应用的停顿时间变长。这里有个文章详细的说明了这种情况:https://engineering.linkedin.com/blog/2016/02/eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic。当IO负载很重的时候,real time会明显比user time长,比如:

[Times: user=0.20 sys=0.01, real=18.45 secs]

如果发生了这种情况,可以这么办:
a:如果是你的应用导致的,优化你的代码
b:如果是别的进程导致的,把它杀掉或者迁走
c:把你的应用迁到一个IO负载小的机器上
tip:如何来监控IO负载?在linux上可以用sar命令来监控IO的负载:sar -d -p 1,这个命令每隔一秒会打印一次每秒的读写数量。这里有sar的详细的用法:https://www.linuxtechi.com/generate-cpu-memory-io-report-sar-command/

7.显式调用了System.gc()

当调用了System.gc()或者是Runtime.getRuntime().gc()以后,就会导致FullGC。FullGC的过程当中,整个JVM是暂停的(所有的应用都被暂停掉)。System.gc()可能是以下几种情况产生的:
a:应用的程序员手动调用了System.gc()
b:应用引用的三方库或者框架甚至是应用服务器可能调用了System.gc()
c:可能是由外部使用了JMX的工具触发,比如:JVisualVM。
d:如果你的应用使用了RMI,RMI会每隔一段时间调用一次System.gc(),这个时间间隔是可以设置的:

– Dsun.rmi.dgc.server.gcInterval=n
– Dsun.rmi.dgc.client.gcInterval=n

要评估一下,是否真的有必要明确调用System.gc()。如果没有必要,就不要调用。同时,你也可以通过给JVM传递‘-XX:+DisableExplicitGC‘参数来禁用掉System.gc()。关于System.gc()的问题和解决方案可以参考:https://blog.gceasy.io/2016/11/22/system-gc/
tip:如何知道是否手动调用了System.gc()?可以把GC日志上传到gceasy,如果有手动调用System.gc(),在‘GC Causes’中就会展示出来,如图:
在这里插入图片描述上图说明发生了4次System.gc()调用。

8.堆内存过大

堆内存过大也会导致GC停顿时间过长,如果堆内存过大,那么堆中就会累计过多的垃圾,当发生FullGC要回收所有的垃圾的时候,就会花费更多的时间。如果你的JVM的堆内存有18G,可以考虑分成3个6G的JVM实例,堆内存小会降低GC的停顿时间。
注意:在应用以上任何一种策略之前,都需要做好测试,这些策略对你可能都不适用,如果使用不当可能带来负面效果。

9.GC任务分配不均

就算有多个GC线程,线程之间的任务分配可能也不是均衡的,这个可能有很多种原因:
a:扫描大的线性的数据结构目前是无法并行的。
b:有些GC事件只发生在单个线程上,比如CMS中的‘concurrent mode failure’。如果你碰巧使用的CMS,可以使用-XX:+CMSScavengeBeforeRemark 这个参数,它可以让多个GC线程之间任务分配的更平均。

英文原文:https://blog.gceasy.io/2016/11/22/reduce-long-gc-pauses/

如果感觉有用,欢迎扫描文章开头的二维码加关注。

这篇关于JVM的GC停顿时间过长该怎么处理?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/373703

相关文章

Java StringBuilder 实现原理全攻略

《JavaStringBuilder实现原理全攻略》StringBuilder是Java提供的可变字符序列类,位于java.lang包中,专门用于高效处理字符串的拼接和修改操作,本文给大家介绍Ja... 目录一、StringBuilder 基本概述核心特性二、StringBuilder 核心实现2.1 内部

SpringBoot AspectJ切面配合自定义注解实现权限校验的示例详解

《SpringBootAspectJ切面配合自定义注解实现权限校验的示例详解》本文章介绍了如何通过创建自定义的权限校验注解,配合AspectJ切面拦截注解实现权限校验,本文结合实例代码给大家介绍的非... 目录1. 创建权限校验注解2. 创建ASPectJ切面拦截注解校验权限3. 用法示例A. 参考文章本文

Java中字符编码问题的解决方法详解

《Java中字符编码问题的解决方法详解》在日常Java开发中,字符编码问题是一个非常常见却又特别容易踩坑的地方,这篇文章就带你一步一步看清楚字符编码的来龙去脉,并结合可运行的代码,看看如何在Java项... 目录前言背景:为什么会出现编码问题常见场景分析控制台输出乱码文件读写乱码数据库存取乱码解决方案统一使

Java Stream流与使用操作指南

《JavaStream流与使用操作指南》Stream不是数据结构,而是一种高级的数据处理工具,允许你以声明式的方式处理数据集合,类似于SQL语句操作数据库,本文给大家介绍JavaStream流与使用... 目录一、什么是stream流二、创建stream流1.单列集合创建stream流2.双列集合创建str

springboot集成easypoi导出word换行处理过程

《springboot集成easypoi导出word换行处理过程》SpringBoot集成Easypoi导出Word时,换行符n失效显示为空格,解决方法包括生成段落或替换模板中n为回车,同时需确... 目录项目场景问题描述解决方案第一种:生成段落的方式第二种:替换模板的情况,换行符替换成回车总结项目场景s

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性