热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

排查所(网上排查)

线上服务的GC问题,是Java程序非常典型的一类问题,非常考验工程师排查问题的能力。同时,几乎是面试必考题,但是能真正答好此题的人并不多,要么原理没吃透,要么缺乏实战经验。过去半年


在线服务的GC问题是Java程序的一个典型问题,考验着工程师解决问题的能力。同时,这几乎是面试的必答题,但真正能很好回答这个问题的人并不多,要么是没有透彻理解原理,要么是缺乏实践经验。


在过去的半年中,我们的广告系统中出现了许多与GC相关的在线问题,例如Full GC过于频繁,Young GC耗时过长。这些问题的影响是:程序卡在GC进程中,进一步导致服务超时从而影响广告收入。


在本文中,我将详细介绍一个经常在网上出现的FGC GC故障排除过程。另外,我会结合GC的运行原理给出一个实用指南,希望对大家有所帮助。内容分为以下三个部分:


从f GC一个常见的网络案例谈气相色谱的工作原理,并介绍排除FGC问题的实用指南


01从网上经常出现的FGC案例开始


去年10月,我们的广告召回系统在该计划上线后频繁收到来自FGC的系统警报。从下面的监控图可以看出,FGC平均每35分钟进行一次。在节目上线之前,我们的FGC频率大约是每两天一次。接下来,详细介绍该问题的故障排除过程。




1.检查JVM配置


通过以下命令检查JVM的启动参数:


PS aux | grep ' application NAmE=ad search '


-Xms4g -Xmx4g -Xmn2g -Xss1024K


-XX:ParallelGCThreads=5


-XX : useConcMarkswepgc


-XX: UseParNewGC


-XX : useCmscompactfullcollection


-xx: CMS initiatiOnccupincreaction=80


可以看到堆内存是4G,粗糙的机器猫是2G,老机器猫是2G。粗糙的机器猫使用ParNew收集器,旧机器猫使用带有并发标记清除的CMS收集器。当老机器猫的内存占用率达到80%时,就会执行FGC。


根据jmap -heap 7276 | head -n20统计,粗糙机器猫一代的伊甸园面积为1.6G,S0和s 1的面积均为0.2G


2.观察老年时的记忆变化。


通过观察老年时的使用情况,可以看到FGC之后,内存可以恢复到500M左右,所以排除了内存泄漏。




3.通过jmap命令检查堆内存中的对象。


用命令jmap-hist 7276 | head-n20




在上图中,活动对象的实例数、占用内存和类名按对象占用内存大小的顺序显示。可见第一名是:int[],它的内存大小远远超过了其他生命体。此时,我们已经将嫌疑人锁定在int[]。


4.进一步分析转储堆内存文件。


int[]被锁定后,我们打算转储内存文件,并通过可视化工具进一步跟踪对象的来源。考虑到在堆转储期间程序将被挂起,我们首先从服务管理平台中删除该节点,然后通过以下命令转储堆内存:


jmap -dump:format=b,file=heap 7276


通过JVisualVM工具从dump导入堆内存文件,还可以看到每个对象占用的空间,其中int[]占内存的50%以上。再往下,可以找到int[]所属的业务对象,发现它来自架构团队提供的codis basic组件。




5.通过代码分析可疑对象


通过代码分析,codis basic组件会生成一个大小约为每分钟40M的int数组来计数TP99和TP90,数组的生命周期为一分钟。根据观察老年记忆变化的第二步,发现老年记忆基本上每分钟增加40M以上,由此推断40 m int数组应该从粗糙的机器猫一代提升到老年。


我们进一步查看了YGCs的频率监控,从下图可以看到一分钟左右的YGCs大概有8个,基本验证了我们的推断:因为CMS收集器默认的世代年龄是6倍,也就是YGCs 6倍之后存活下来的对象会提升到老年,而codis组件中大数组的生命周期是1分钟,正好符合这个要求。

tiaoimg.com/origin/pgc-image/90550f7c448643f2925537ec3234dfc3?from=pc">

至此,整个排查过程基本结束了,那为什么程序上线前没出现此问题呢?通过上图可以看到:程序上线前YGC的频次在5次左右,此次上线后YGC频次变成了8次左右,从而引发了此问题。

6. 解决方案

为了快速解决问题,我们将CMS收集器的分代年龄改成了15次,改完后FGC频次恢复到了2天一次,后续如果YGC的频次超过每分钟15次还会再次触发此问题。当然,我们最根本的解决方案是:优化程序以降低YGC的频率,同时缩短codis组件中int数组的生命周期,这里就不做展开了。

02 GC的运行原理介绍

上面整个案例的分析过程中,其实涉及到很多GC的原理知识,如果不懂得这些原理就着手处理,其实整个排查过程是很抓瞎的。

这里,我选择几个最核心的知识点,展开介绍下GC的运行原理,最后再给出一份实践指南。

1. 堆内存结构

大家都知道: GC分为YGC和FGC,它们均发生在JVM的堆内存上。先来看下JDK8的堆内存结构:

可以看到,堆内存采用了分代结构,包括粗犷的机器猫代和老年代。粗犷的机器猫代又分为:Eden区,From Survivor区(简称S0),To Survivor区(简称S1区),三者的默认比例为8:1:1。另外,粗犷的机器猫代和老年代的默认比例为1:2。

堆内存之所以采用分代结构,是考虑到绝大部分对象都是短生命周期的,这样不同生命周期的对象可放在不同的区域中,然后针对粗犷的机器猫代和老年代采用不同的垃圾回收算法,从而使得GC效率最高。

2. YGC是什么时候触发的?

大多数情况下,对象直接在年轻代中的Eden区进行分配,如果Eden区域没有足够的空间,那么就会触发YGC(Minor GC),YGC处理的区域只有粗犷的机器猫代。因为大部分对象在短时间内都是可收回掉的,因此YGC后只有极少数的对象能存活下来,而被移动到S0区(采用的是复制算法)。

当触发下一次YGC时,会将Eden区和S0区的存活对象移动到S1区,同时清空Eden区和S0区。当再次触发YGC时,这时候处理的区域就变成了Eden区和S1区(即S0和S1进行角色交换)。每经过一次YGC,存活对象的年龄就会加1。

3. FGC又是什么时候触发的?

下面4种情况,对象会进入到老年代中:

YGC时,To Survivor区不足以存放存活的对象,对象会直接进入到老年代。经过多次YGC后,如果存活对象的年龄达到了设定阈值,则会晋升到老年代中。动态年龄判定规则,To Survivor区中相同年龄的对象,如果其大小之和占到了 To Survivor区一半以上的空间,那么大于此年龄的对象会直接进入老年代,而不需要达到默认的分代年龄。大对象:由-XX:PretenureSizeThreshold启动参数控制,若对象大小大于此值,就会绕过粗犷的机器猫代, 直接在老年代中分配。

当晋升到老年代的对象大于了老年代的剩余空间时,就会触发FGC(Major GC),FGC处理的区域同时包括粗犷的机器猫代和老年代。除此之外,还有以下4种情况也会触发FGC:

老年代的内存使用率达到了一定阈值(可通过参数调整),直接触发FGC。空间分配担保:在YGC之前,会先检查老年代最大可用的连续空间是否大于粗犷的机器猫代所有对象的总空间。如果小于,说明YGC是不安全的,则会查看参数 HandlePromotionFailure 是否被设置成了允许担保失败,如果不允许则直接触发Full GC;如果允许,那么会进一步检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果小于也会触发 Full GC。Metaspace(元空间)在空间不足时会进行扩容,当扩容到了-XX:MetaspaceSize 参数的指定值时,也会触发FGC。System.gc() 或者Runtime.gc() 被显式调用时,触发FGC。

4. 在什么情况下,GC会对程序产生影响?

不管YGC还是FGC,都会造成一定程度的程序卡顿(即Stop The World问题:GC线程开始工作,其他工作线程被挂起),即使采用ParNew、CMS或者G1这些更先进的垃圾回收算法,也只是在减少卡顿时间,而并不能完全消除卡顿。

那到底什么情况下,GC会对程序产生影响呢?根据严重程度从高到底,我认为包括以下4种情况:

FGC过于频繁:FGC通常是比较慢的,少则几百毫秒,多则几秒,正常情况FGC每隔几个小时甚至几天才执行一次,对系统的影响还能接受。但是,一旦出现FGC频繁(比如几十分钟就会执行一次),这种肯定是存在问题的,它会导致工作线程频繁被停止,让系统看起来一直有卡顿现象,也会使得程序的整体性能变差。YGC耗时过长:一般来说,YGC的总耗时在几十或者上百毫秒是比较正常的,虽然会引起系统卡顿几毫秒或者几十毫秒,这种情况几乎对用户无感知,对程序的影响可以忽略不计。但是如果YGC耗时达到了1秒甚至几秒(都快赶上FGC的耗时了),那卡顿时间就会增大,加上YGC本身比较频繁,就会导致比较多的服务超时问题。FGC耗时过长:FGC耗时增加,卡顿时间也会随之增加,尤其对于高并发服务,可能导致FGC期间比较多的超时问题,可用性降低,这种也需要关注。YGC过于频繁:即使YGC不会引起服务超时,但是YGC过于频繁也会降低服务的整体性能,对于高并发服务也是需要关注的。

其中,「FGC过于频繁」和「YGC耗时过长」,这两种情况属于比较典型的GC问题,大概率会对程序的服务质量产生影响。剩余两种情况的严重程度低一些,但是对于高并发或者高可用的程序也需要关注。

03 排查FGC问题的实践指南

通过上面的案例分析以及理论介绍,再总结下FGC问题的排查思路,作为一份实践指南供大家参考。

1. 清楚从程序角度,有哪些原因导致FGC?

大对象:系统一次性加载了过多数据到内存中(比如SQL查询未做分页),导致大对象进入了老年代。内存泄漏:频繁创建了大量对象,但是无法被回收(比如IO对象使用完后未调用close方法释放资源),先引发FGC,最后导致OOM.程序频繁生成一些长生命周期的对象,当这些对象的存活年龄超过分代年龄时便会进入老年代,最后引发FGC. (即本文中的案例)程序BUG导致动态生成了很多新类,使得 Metaspace 不断被占用,先引发FGC,最后导致OOM.代码中显式调用了gc方法,包括自己的代码甚至框架中的代码。JVM参数设置问题:包括总内存大小、粗犷的机器猫代和老年代的大小、Eden区和S区的大小、元空间大小、垃圾回收算法等等。

2. 清楚排查问题时能使用哪些工具

公司的监控系统:大部分公司都会有,可全方位监控JVM的各项指标。JDK的自带工具,包括jmap、jstat等常用命令:# 查看堆内存各区域的使用率以及GC情况jstat -gcutil -h20 pid 1000# 查看堆内存中的存活对象,并按空间排序jmap -histo pid | head -n20# dump堆内存文件jmap -dump:format=b,file=heap pid可视化的堆内存分析工具:JVisualVM、MAT等

3. 排查指南

查看监控,以了解出现问题的时间点以及当前FGC的频率(可对比正常情况看频率是否正常)了解该时间点之前有没有程序上线、基础组件升级等情况。了解JVM的参数设置,包括:堆空间各个区域的大小设置,粗犷的机器猫代和老年代分别采用了哪些垃圾收集器,然后分析JVM参数设置是否合理。再对步骤1中列出的可能原因做排除法,其中元空间被打满、内存泄漏、代码显式调用gc方法比较容易排查。针对大对象或者长生命周期对象导致的FGC,可通过 jmap -histo 命令并结合dump堆内存文件作进一步分析,需要先定位到可疑对象。通过可疑对象定位到具体代码再次分析,这时候要结合GC原理和JVM参数设置,弄清楚可疑对象是否满足了进入到老年代的条件才能下结论。

最后的话

这篇文章通过线上案例并结合GC原理详细介绍了FGC的排查过程,同时给出了一份实践指南。

后续会以类似的方式,再分享一个YGC耗时过长的案例,希望能帮助大家吃透GC问题排查,如果觉得本文对你有帮助,请帮忙转发或者点个再看!

转载于:https://mp.weixin.qq.com/s/Hs2bo37x7mcx7XTdNQVgZQ


推荐阅读
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • 这个问题困扰了我两天,卸载Dr.COM客户端(我们学校上网要装这个客户端登陆服务器,以后只能在网页里输入用户名和密码了),问题解决了。问题的现象:在实验室机台式机上安装openfire和sp ... [详细]
  • 生产环境下JVM调优参数的设置实例
     正文前先来一波福利推荐: 福利一:百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。福利二 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • 本文介绍了Foundation框架中一些常用的结构体和类,包括表示范围作用的NSRange结构体的创建方式,处理几何图形的数据类型NSPoint和NSSize,以及由点和大小复合而成的矩形数据类型NSRect。同时还介绍了创建这些数据类型的方法,以及字符串类NSString的使用方法。 ... [详细]
  • 浅解XXE与Portswigger Web Sec
    XXE与PortswiggerWebSec​相关链接:​博客园​安全脉搏​FreeBuf​XML的全称为XML外部实体注入,在学习的过程中发现有回显的XXE并不多,而 ... [详细]
  • 找到JDK下载URL当然去官网找了。目前最新的1.8的下载URL(RPM)如下:http:download.oracle.comotn-pubjavajdk8u161-b122f3 ... [详细]
author-avatar
喵屎柔_
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有