“堆内存持续占用高 且 ygc回收效果不佳” 排查处理实践
csdh11 2025-01-10 12:44 3 浏览
作者:京东零售 王江波
说明:部分素材来源于网络,数据分析全为真实数据。
一、 问题背景
自建的两套工具,运行一段时间后均出现 内存占用高触发报警,频繁young gc且效果不佳。曾经尝试多次解决,因各种原因耽搁,最近下定决心处理此问题。
二、 问题描述
Q:堆内存1018M,使用达到950M左右触发一次young gc,ygc之后内存占用630M,未发生full gc
三、 容器配置
已处理关键信息
?主机名 xxx
?实例ID xxx
?ip
?操作系统名称Linux
?操作系统体系结构amd64
?CPU个数2
?JRE版本1.8.0_191
?JVM启动时间2023-02-18 17:14:10.873
?启动路径/export/App
?Full GCPS MarkSweep
?Young GCPS Scavenge
?进程ID115135
?物理内存大小251.4GB(269888389120Byte)
?交换区大小0.0GB(0Byte)
?虚拟内存大小12.5GB(13368197120Byte)
?应用路径/export/App/lib/tp-center-web.jar!/BOOT-INF/lib/
?JVM启动参数-javaagent:/export/xxx/lib/pfinder-profiler-agent-1.0.8-20210322032622-6f12bda2.jar -Ddeploy.app.name=xxx -Ddeploy.app.id=xxx -Ddeploy.instance.id=0 -Ddeploy.instance.name=server1 -DJDOS_DATACENTER=HT -Dloader.path=./conf -Dspring.profiles.active=pre -Xms1024m -Xmx1024m -Xmn384m -XX:MetaspaceSize=64m -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly -XX:ParallelGCThreads=2 -XX:CICompilerCount=2 -XX:MaxDirectMemorySize=128m -Duser.timezone=Asia/Shanghai -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=xxx -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=xxx
四、 问题分析
1、 young gc的时机?
R:ygc在新生代的eden区满了之后就会触发,采用复制算法回收新生代的垃圾。
2、 为何young gc后堆内存使用率仍然很高?
R:新生代过小,大对象直接进入老年代,编码时要尽量减少大对象的使用。
3、 full gc 的时机?
R:HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代
1) 发生fgc之前进行检查,如果 老年代可用的连续内存空间 < 新生代历次ygc后升入老年代的对象总和的平均大小,此时先触发old gc,然后执行ygc
2) 执行ygc之后有一批对象需要放入老年代,此时老年代没有足够空间存放对象,必须触发一次ogc
3) 老年代内存使用率超过阈值,也要触发ogc
4) 元空间不足时也会触发一次
4、 什么原因导致内存占用高?
R:heap dump分析、gc日志
五、 回收器
?吞吐量优先(Parallel Scavenge):新生代收集器,侧重于吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+运行垃圾收集时间))的控制。
?Serial Old:Serial Old是Serial收集器的老年代版本, 它同样是一个单线程收集器, 使用标记-整理算法:由于老年代 Serial Old 收集器在服务端应用性能上的“拖累”, 使用Parallel Scavenge 收集器也未必能在整体上获得吞吐量最大化的效果。由于单线程的老年代收集中无法充分利用服务器多处理器的并行处理能力, 在老年代内存空间很大而且硬件规格比较高级的运行环境中, 这种组合的总吞吐量甚至不一定比ParNew加CMS的组合来得优秀。
?Parallel Old :是 Parallel Scavenge 收集器的老年代版本, 支持多线程并发收集, 基于标记-整理算法实现. 这个收集器是直到 JDK 6 时才开始提供的, 在此之前, 新生代的 Parallel Scavenge收集器一直处于相当尴尬的状态, 原因是如果新生代选择了 Parallel Scavenge 收集器, 老年代除了 Serial Old(PS MarkSweep)收集器以外别无选择
UseParallelGC: JDK9 之前虚拟机运行在 Server 模式下的默认值, 使用 ParallelScavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收。
UseParallelOldGC: 打开此开关后, 使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收。
?ParNew收集器与Parallel收集器类似:此收集器是许多运行在Server模式下的虚拟机的首选,除了Serial收集器外,只有它能与CMS收集器配合工作
?并发标记清除(CMS)回收器:是一种以获取最短回收停顿时间为目标的回收器,该回收器是基于“标记-清除”算法实现的。注重回收时产生的停顿时间,是停顿时间最短的收集器。非常适合注重用户体验的应用上,是Hotspot虚拟机第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程几乎同时运行
六、 优化策略
1、 加gc日志
-XX:+PrintGCDetails // 创建详细的gc日志
-XX:+PrintGCTimeStamps
-Xloggc:/export/Logs/com/gc.log // gc日志文件名
-XX:+UseGCLogFileRotation // 限制保存在gc日志中的数据量
-XX:GCLogFileSize=10M // 日志文件大小
-XX:NumberOfGCLogFiles=10 // 日志文件个数
-XX:+HeapDumpAfterFullGC // fullgc前dump文件保存
-XX:HeapDumpPath=/export/Logs/com/fgcdump.log // dump文件保存路径
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=目录 // oom时生成dump文件
// ================================= 附加参数如下 =============================================
// 可以使用-XX:DisableExplicitGC选项禁用显示调用Full gc(system.gc在cms gc下我们通过-XX:+ExplicitGCInvokesConcurrent来做一次稍微高效点的GC(效果比Full GC要好些))
-XX:+UnlockExperimentalVMOptions // 解锁实验参数,允许使用实验性参数,JVM中有些参数不能通过-XX直接复制需要先解锁,比如要使用某些参数的时候,可能不会生效,需要设置这个参数来解锁
-XX:+UseParNewGC // 真正的并发收集器,与CMS搭配使用(ParNew收集器是Serial收集器的多线程版本,使用这个参数后会在新生代进行并行回收,老年代仍旧使用串行回收。新生代S区任然使用复制算法)
-XX:+UseConcMarkSweepGC // 和应用程序线程一起执行,相对于Stop The World来说虚拟机停顿时间较少。停顿减少,吞吐量会降低。它使用的是 标记清除算法,运作过程为四个步骤,分别是 初始标记—并发标识—重新标记—并发清除。它是老年代的收集算法,新生代使用ParNew收集算法。默认关闭
CMS收集器的缺点是对服务器CPU资源较为敏感,在并发标记时会降低吞吐量。它使用的标记清除算法也会产生大量空间碎片,空间碎片的存在会加大Full GC的频率,虽然老年代还有足够的内存,但是因为内存空间不连续,不得不进行Full GC
-XX:CMSInitiatingOccupancyFraction=75 // 允许最大占用占用率 -XX:+UseCMSInitiatingOccupancyOnly // 只用设定的回收阈值,如果不指定,JVM仅在第一次使用设定值,后续则自动调整
-XX:+ExplicitGCInvokesConcurrent // ExplicitGCInvokesConcurrent这个参数是配合CMS使用的,开启后System.gc()还是会触发Full GC,不过并不是一个完全的stop-the-world的Full GC,而是并发的CMS GC
-XX:+ParallelRefProcEnabled // 可以用来并行处理 Reference,以加快处理速度,缩短耗时 -XX:+CMSParallelRemarkEnabled // 在CMS GC前启动一次ygc,目的在于减少old gen对ygc gen的引用,降低remark时的开销-----一般CMS的GC耗时 80%都在remark阶段
// 切记以下参数不要乱加
-XX:+UseCMSCompactAtFullCollection // Full GC后,进行一次整理,整理过程是独占的,会引起停顿时间变长。仅在使用CMS收集器时生效。
-XX:CMSFullGCsBeforeCompaction // 默认为 0,就是每次FullGC都对老年代进行碎片整理压缩,建议保持默认
2、 gc日志指标
S0: 新生代中Survivor space 0区已使用空间的百分比
S1: 新生代中Survivor space 1区已使用空间的百分比 E: 新生代已使用空间的百分比 O: 老年代已使用空间的百分比 M: 元空间已使用空间的百分比
CCS: 压缩类空间利用率百分比 YGC: 从应用程序启动到当前,发生Yang GC 的次数
YGCT: 从应用程序启动到当前,Yang GC所用的时间【单位秒】 FGC: 从应用程序启动到当前,发生Full GC的次数 FGCT: 从应用程序启动到当前,Full GC所用的时间 GCT: 从应用程序启动到当前,用于垃圾回收的总时间【单位秒】
3、 最大线程数
系统可创建最大线程数 = (机器本身可用内存 - JVM分配的堆内存 - JVM元数据区)/ 线程栈大小
4、 内存分析
4.1 常用命令
4.1.1 jstack
// Java堆栈跟踪工具,它用于打印出给定的java进程ID、core file、远程调试服务的Java堆栈信息.
jstack命令用于生成虚拟机当前时刻的线程快照线程快照是当前虚拟机内每一条线程正在执行的方法堆栈的集合,使用其主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待等问题线程出现停顿的时候通过jstack来查看线程的调用堆栈,就可以知道没有响应的线程到底在做什么事,或者等待什么资源若java程序崩溃生成core文件,jstack可用来获得core文件的java stack和native stack的信息
还可附属到正在运行的java程序上,看到当前运行的java程序的java stack和native stack信息,若呈现hung状态,jstack很有用
应用:jstack [option] <pid> // 打印某个进程的堆栈信息
?线程状态
?Monitor监视锁
item1:实战案例1 jstack分析死锁问题
死锁指的是两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,将无法继续进行
jps:终端中输入jsp查看当前运行的java程序
8896
5684 JUnitStarter
17836 Jps
19804 Launcher
文件分析:【本文件未死锁,仅用于分析,死锁时会出现 线程1持有A等B,线程2持有B等A】
"consumer_monitor_report_tp_pre_1_1_3_1677132947525" #7851 daemon prio=5 os_prio=0 tid=0x00007f8d2001b000 nid=0x31c8c waiting on condition [0x00007f8ceeeef000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000fad6df60> (a java.util.concurrent.CountDownLatch$Sync) ... Locked ownable synchronizers: - None
// consumer_monitor_report_tp_pre_1_1_3_1677132947525 的线程处于TIMED_WAITING状态,持有 - None 锁(无锁),等待 0x00000000fad6df60 的锁
item2:实战案例2 jstack分析cp过高问题
?通过 top 命令查看各个进程cpu使用情况,默认按cpu使用率高到低排序
?通过 top -Hp pid 查看该进程下,各个线程的cpu使用情况
?定位到cpu占用率高的线程之后,使用 jstack pid 命令查看当前java进程的堆栈状态
?nid=0x31c8b 进制转换,文件中,每个线程都有一个nid,查看状态
4.1.2 jstat
// java虚拟机统计信息工具,利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控
远程监控:jstat -gcutil port@ip
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 81.75 24.57 94.43 95.36 92.41 5649 82.696 2 0.649 83.346 0.00 81.75 26.15 94.43 95.36 92.41 5649 82.696 2 0.649 83.346 0.00 81.75 27.19 94.43 95.36 92.41 5649 82.696 2 0.649 83.346 0.00 81.75 28.81 94.43 95.36 92.41 5649 82.696 2 0.649 83.346 0.00 81.75 29.91 94.43 95.36 92.41 5649 82.696 2 0.649 83.346 |
说明:GCT = YGCT + FGCT
4.1.3 jmap-histo
// 查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象
包括对 Heap size 和 垃圾回收状况的监控
4.1.4 jmqp-heap
// 查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况
4.1.5 jmqp-dump
// 生成堆快照(瞬时)
利用jvisualvm分析内存dump文件:点击文件->装入->文件类型选“堆”
Visual VM的OQL语言是对HeapDump进行查询,类似于SQL的查询语言,它的基本语法如下:
select <JavaScript expression to select>
[ from [instanceof] <class name> <identifier>
[ where <JavaScript boolean expression to filter> ] ]
OQL由3个部分组成:select子句、from子句和where子句。select子句指定查询结果要显示的内容。from子句指定查询范围,可指定类名,如java.lang.String、char[]、[Ljava.io.File;(File数组)。where子句用于指定查询条件。
4.2 加日志后
见调优方案
七、 调优方案1:增大新生代
1、 解析gc日志
元空间分配64M(不足自动调整了),新生代改为480m
?25.939: [Full GC (Metadata GC Threshold) [PSYoungGen: 4133K->0K(461312K)] [ParOldGen: 34773K->36327K(557056K)] 38907K->36327K(1018368K), [Metaspace: 62967K->62967K(1105920K)], 0.3564880 secs] [Times: user=0.72 sys=0.02, real=0.36 secs]
25.939 | Full GC (Metadata GC Threshold) | GC (Allocation Failure) | GC (GCLocker Initiated GC) | PSYoungGen: 4133K->0K(461312K) | ParOldGen: 34773K->36327K(557056K) | 38907K->36327K(1018368K) | Metaspace: 62967K->62967K(1105920K) | 0.3564880 secs | user=0.72 | sys=0.02 | real=0.36 secs |
容器已启动时间 | 发生一次 fgc(元空间不够用导致) | 年轻代无足够空间 | openJDK的一个bug,只是一直没被修复(https://bugs.openjdk.org/browse/JDK-8048556),不用过于关注 | 默认总容量为 0.9*新生代 | 数据变大因为新生代的垃圾进来了 | GC前堆内存已使用容量→GC堆内存容量(堆内存总容量 = 0.9*新生代 + 老年代) | 元空间未回收 | 整个GC花费时间 | CPU工作在用户态时间 | CPU工作在内核态时间 | GC总时间 |
八、 调优方案2:增大元空间 + CMS & PNEW
以下方案暂时没啥问题
九、 临时方案以观后效
说明:去批量处理,降低对象大小,也许也是一个不错的方案,待验证。
部署稳定后jvm监控图表如下:
相关推荐
- JNDI注入详解
-
JNDI简介JNDI是java命名与目录接口(javaNamingandDirectoryInterface),在J2EE规范中是重要的规范之一。通过调用JNDI的API应用程序可以定位资源和...
- Java 近期新闻:Hibernate 6.0、JobRunr 5.0、JHipster 7.8.0
-
本期Java近期新闻综述内容涉及JDK19、SpringBoot、SpringCVEs、ApacheTomcat点版本、QuarkusToolsforVisualStudio...
- 2023年200多道Java基础面试题
-
最近有很多人后台问我,有什么方法能够快速提升自己,通过阿里、腾讯、字节跳动、京东等互联网大厂的面试,我觉得短时间提升自己最快的手段就是背面试题,最近总结了Java常用的面试题,分享给大家,希望大家都能...
- 完全零基础入门Fastjson系列漏洞
-
一、前置知识1.fastjson怎么用?fastjson是啥百度就有,看了之后不熟悉的人还是会一脸懵逼,我们可以通过以下这个小例子来快速学会使用...
- 解密阿里线上问题诊断工具Arthas和jvm-sandbox
-
大纲目录这篇文章是之前学习Arthas和jvm-sandbox的一些心得和总结,希望能帮助到大家。本文字较多,可以根据目录进行对应的阅读。背景:现在的问题所在?Arthas:Arthas能帮助你干什...
- Java 服务 Docker 容器化最佳实践
-
一、概述当我们在容器中运行Java应用程序时,可能希望对其进行调整参数以充分利用资源。...
- “堆内存持续占用高 且 ygc回收效果不佳” 排查处理实践
-
作者:京东零售王江波说明:部分素材来源于网络,数据分析全为真实数据。一、问题背景自建的两套工具,运行一段时间后均出现内存占用高触发报警,频繁younggc且效果不佳。曾经尝试多次解决,因各种原...
- log4j2 JNDI注入分析笔记
-
前言ApacheLog4j2是一款优秀的Java日志框架,最近爆出了一个jndi注入的漏洞,影响面非常广,各大厂商都被波及。Log4j2作为日志记录的第三方库,被广泛得到使用,这次主要分享一下,最近...
- Linux-常用操作命令介绍
-
1.帮助命令1.1help命令...
- 基于容器的Java内存参数解析
-
在基于物理的服务器(此处主要与容器平台进行区分,故此描述)上运行Java应用程序时,我们通常会使用Java虚拟机参数"-Xms、-Xmx"来指定Java堆内存的初始值和最大值。如果要将...
- 用于处理 PDF 文档的开放源码 Java 工具
-
哈喽,我是老鱼,一名致力于在技术道路上的终身学习者、实践者、分享者!...
- Log4j 严重漏洞修最新修复方案参考
-
CVE-2021-44228,原理上是log4j-core代码中的JNDI注入漏洞。这个漏洞可以直接导致服务器被入侵,而且由于“日志”场景的特性,攻击数据可以多层传导,甚至可以威胁到纯内网的服...
- JVM性能监控工具
-
生产环境慎用的命令JDK中带有了一堆的工具是可以用来查看运行状况,排查问题的,但对于这些工具还是要比较清楚执行后会发生什么,否则有可能会因为执行了一个命令就导致严重故障,重点讲下影响比较大的jmap。...
- 一招教你在linux服务器配置Jenkins持续集成神器
-
01配置插件...
- 谈JVM xmx, xms等内存相关参数合理性设置
-
作者:京东零售刘乐上一篇文章说到JVM垃圾回收算法的两个优化标的:吞吐量和停顿时长,并提到这两个优化目标是有冲突的。那么有没有可能提高吞吐量而不影响停顿时长,甚至缩短停顿时长呢?答案是有可能的,提高...
- 一周热门
-
-
一文读懂关于MySQL Datetime字段允许插入0000-00-00无效日期
-
MySQL数据库关于表的一系列操作 mysql 表操作
-
IDC机房服务器托管可提供的服务
-
新版腾讯QQ更新Windows 9.9.7、Mac 6.9.25、Linux 3.2.5版本
-
一款全能的看图软件,速度快、功能强、免费用
-
深度测评:Pixave 和图片管理之间的距离(二)
-
Boston Dynamics Founder to Attend the 2024 T-EDGE Conference
-
Serv-u 提权
-
一文看懂mysql时间函数now()、current_timestamp() 和sysdate()
-
详解PostgreSQL 如何获取当前日期时间
-
- 最近发表
- 标签列表
-
- huaweiupdateextractor (27)
- mysql 时间索引 (31)
- mydisktest_v298 (34)
- document.appendchild (35)
- 头像打包下载 (61)
- acmecadconverter_8.52绿色版 (39)
- oracle timestamp比较大小 (28)
- word文档批量处理大师破解版 (36)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- 加密与解密第四版pdf (30)
- jemeter官网 (31)
- parsevideo (33)
- 个人网站源码 (37)
- exe4j_java_home (30)
- centos7.4下载 (33)
- xlsx.full.min.js下载 (32)
- 深度学习 pdf (28)
- mysql 查询今天的数据 (34)
- intouch2014r2sp1永久授权 (36)
- 先锋影音源资2019 (35)
- usb2.0-serial驱动下载 (30)
- vs2010官网 (31)
- python核心编程第四版pdf (32)
- jdk1.8.0_191下载 (33)