首页 > 其他分享 >网站持久代引发Full GC问题分析

网站持久代引发Full GC问题分析

时间:2024-04-03 22:22:57浏览次数:212  
标签:Full 持久 GC JVM line Class 加载

  现状: Dragoon(监控系统)的日报显示trade_us_wholelsale(美国wholesale集群),日均Young GC次数25w次左右,应用暂停295w毫秒(相当于40多分钟),Full GC次数600次左右,应用暂停190w毫秒(相当于30多分钟)。 GC,尤其是Full GC,每次都会导致JVM暂停工作,处理垃圾回收任务,短时间内无法响应用户请求,大量的Full GC会导致系统响应速度降低,而且引来OOM的巨大风险。 分析: 启动参数: trade_us_wholesale应用的JVM启动参数如下: -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k-XX:+UseConcMarkSweepGC 堆内存=新生代+老年代:-Xmx2g -Xms2g 新生代:256M 老年代:1792M(2G-256M) 持久代:-XX:PermSize=128M 栈内存:-Xss256KB 新生代回收方式:并行回收 老年代回收方式:并发回收(CMS) 实时数据分析: 数据来源于Dragoon平台 l  分代内存占比

 

 
JVM分代 使用内存量 内存总量
持久代 117M-125M 128M
新生代 220M-225M 256M
旧生代 800M-1G 1792M
注:明显看出,新生代以及持久代,内存使用较为紧张,旧生代较为宽裕。 l  Young GC

 

Young GC频繁,由于并行GC采用多线程回收方式,但是考虑到GC次数很高,由于GC每次都会暂停应用,因此对应用的影响是不可忽视的。 l  Full GC

 

Full GC是指新生代、旧生代、持久代一起进行GC。 引起Full GC的原因主要有三种:
  1. 旧生代内存不足
  2. 持久代内存不足
  3. 统计新生代 GC晋升到旧生代的平均大小大于旧生代的剩余空间
Full GC回收成本高,应用会因此暂停更长时间,无法响应用户的请求。 l  Class Load情况

 

绿线表示卸载类的数量的累加总数,红线表示总共装载过的类的数量的累加总数,蓝线表示当前装载的类的数量的累加总数。绿线和红线的发展趋势是极其一 致的,即有大量的类被加载之后,又被卸载。加载类的信息存放在持久代。这也就从某方面印证了,持久代可能是在反复的加载类和卸载类,什么原因造成这种现 象,GC的可能性较大,我们接着看后续的分析。 GC Log情况 我们截取线上GC log的中的一段进行分析: 2011-08-23T04:24:27.413-0700: 611.162: [Full GC 611.162: [CMS: 985319K->721699K(1835008K), 2.9525910 secs] 1152585K->721699K(2070976K), [CMS Perm : 131071K->103472K(131072K)], 2.9528010 secs] [Times: user=2.87 sys=0.08, real=2.96 secs] l  解释: 持久代:131071K->103472K 总空间:131072K 旧生代:985319K->721699K 总空间:1835008K 堆内存:1152585K->721699K 总空间:2070976K 此次Full GC时间花费2.96秒,在用户态(user)占用CPU百分比2.87,系统态(sys)占用百分比0.08。 l  分析: 数据很清晰了,持久代的GC是导致Full GC的原因,这和我们之前看到的类信息的加载之后又卸载之后又加载的现象是相符的。持久代空间不够,引起GC,GC回收类的信息,但是由于回收的类信息 JVM之后还需要,因此JVM又去加载这些信息。JVM就在这反复加载和卸载中消耗着资源,无暇响应用户的请求。   Unloading Log情况 我们继续观察显示控制台打出的unload的日志,这里打印出卸载类的情况。

 

sun.reflect.GeneratedSerializationConstructorAccessor这是sun的反射机使用到的类,当 事业反射newInstance()时,JVM会根据-Dsun.reflect.noInflation和 -Dsun.reflect.inflationThreshold判断是使用本地代码的反射构造器还是使用动态生成字节码构造出来的构造器,默认配置是 将会使用后者,这种方式的构造上更慢,但运行速度更快,对于使用大量反射的框架来言,后者更加合适。 GeneratedSerializationConstructorAccessor就是动态生成字节码构造出来的,后面的数字是表示累计创建类的数 量。针对一个类,同一个类加载构造器,反射的ConstructorAccessor只有一个。 可是wholesale短短启动不到3小时,怎么会有这么多ConstructorAccessor产生而且还被卸载呢?这些类是为了反射什么类而创建的呢?继续分析,下面只能dump jvm,看看内存中的情况具体是怎样了? 还有一点,线上的预发布机没有这种现象,没有大量的unloading日志,没有Full GC,他们的硬件和软件都是一样的,唯一不同的就是PV,这点很重要。 Dump JVM 我们去线上dump下来jvm的信息,使用MAT打开,看classloader Explorer,发现更多信息。

 

我们看到大量的DelegatingClassLoader,而且看到了GeneratedSerializationConstructorAccessor, 我们查看都有哪些类被加载,很明显JVM当中有大量的GeneratedSerializationConstructorAccessor+count类。

 

这说明JVM当中有很多ConstructorAccessor一大部分被卸载了,一小部分被留在JVM当中。而且,大家注意每个 ConstructorAccessor类都有自己独立的classloader,类信息的卸载规则是按照classloader来卸载,只有这个 loader中加载的所有类都不被使用,这些类信息才能被卸载。线上的信息好像就这么多了,我们看看DelegatingClassLoader能不能在 测试环境中debug到,看看具体是什么问题。 测试环境日志 我们在测试环境搭建一套wholesale环境,使用Jmeter小幅度压测wholesale的detail页面,随着wholesale稳 定,load日志出现明显的规律性现象。每次访问detail页面,都会有 GeneratedSerializationConstructorAccessor类

 

上图就是在10个线程并发访问wholesaledetail页面,load日志中的内容,问题应该是出现在这些类的加载上。我们debug一下,看看具体情况。 我们debug Classloader这个类的构造器,一次访问是有哪些classloader构造出来,最终debug到 DelegatingClassLoader,结合load日志,发现这个classloader加载后就会有 GeneratedSerializationConstructorAccessor被加载。依据堆栈查看调用信息。 我们看到ReflectionConverter的中的一段代码: final Object result = instantiateNewInstance(context, reader.getAttribute(mapper.attributeForReadResolveField())); ReflectionConverter是XStream的反射转换器,转换器是用来编码或解码Java对象的,也就是负责java对象和xml直 接的转换。instantiateNewInstance使用来初始化类的实例的,其中 reader.getAttribute(mapper.attributeForReadResolveField())就是 WholesaleProductPriceXstreamDTO类。 instantiateNewInstance方法中会先去context当中取这个类的实例,如果没有将会使用反射构造出类的实例,在构造实例时,他们创建新的类加载器,并执行下面的一段代码: Constructor customConstructor = reflectionFactory.newConstructorForSerialization(type, javaLangObjectConstructor); 这段代码就是动态字节码创建类构造器的代码,我们debug发现customConstructor对象中正好包含sun.reflect.GeneratedSerializationConstructorAccessor对象。 而这段代码是在我们的业务二方库biz.wsproduct中调用的: XStream xstream = new XStream(); xstream.alias(“root”, List.class); xstream.alias(“record”, WholesaleProductPriceXstreamDTO.class); 问题很清楚了,就是这个XStream,业务代码中错误的new这个类,造成每次执行到这段代码都需要执行上面讲述的一段逻辑,都会load GeneratedSerializationConstructorAccessor类。 而且众所周知,wholesale detail pv巨大(200w以上),大量的类被加载,应该是造成PermGen使用吃紧的原因,而且由于是创造一个新的类加载器,这样一来一次访问结束,就可以回 收这个类加载器,回收其中的类信息,释放部分PermGen空间。这样我们就看到大量的unload出现,而且PermGen虽然空间紧张,但是从未 OOM的问题也就很容易解释了。 整体的堆栈信息如下(部分堆栈信息省略): Daemon Thread [TP-Processor39] (Suspended) DelegatingClassLoader(ClassLoader).(ClassLoader) line: 207 DelegatingClassLoader.(ClassLoader) line: 54 … MethodAccessorGenerator.generate(Class, String, Class[], Class, Class[], int, boolean, boolean, Class) line: 377 MethodAccessorGenerator.generateSerializationConstructor(Class, Class[], Class[], int, Class) line: 95 ReflectionFactory.newConstructorForSerialization(Class, Constructor) line: 313 Sun14ReflectionProvider.getMungedConstructor(Class) line: 61 Sun14ReflectionProvider.newInstance(Class) line: 40 ReflectionConverter.instantiateNewInstance(UnmarshallingContext, String) line: 145 ReflectionConverter.unmarshal(HierarchicalStreamReader, UnmarshallingContext) line: 87 … XStream.unmarshal(HierarchicalStreamReader, Object, DataHolder) line: 521 XStream.unmarshal(HierarchicalStreamReader, Object) line: 509 XStream.fromXML(Reader) line: 475 XStream.fromXML(String) line: 468 WholesaleProductPriceXstreamHelper.convertXmlToWholesaleProductPriceDtoList(String) line: 55 WholesaleDetailUtil.getPriceDetailDTOFormProduct(WsProductDO, String) line: 404 WholesaleDetailUtil.getProductInfoDTOFromProduct(WsProductDO, String) line: 158 WholesaleProductDetail.process(RunData, TemplateContext) line: 282 WholesaleProductDetail(BaseDetailScreen).execute(RunData, TemplateContext) line: 73 WholesaleProductDetail(TemplateModule).execute(RunData) line: 38 … 解决方案: 解决方案比较简单,我们将XStream的设置成static变量,保证JVM当中只有一个这样的实例,在static块当中初始化,代码如下:     private static XStream xstream = null;     static {         xstream = new XStream();         xstream.alias(“root”, List.class);  xstream.alias(“record”, WholesaleProductPriceXstreamDTO.class);     }   解决效果: 根据最新的Dragoon监控日报显示: Full GC的次数由原先的647次降至157次, 应用暂停时间由原来的1943232.0 ms (30分钟左右)降至488444.0 ms (8分钟左右),大大缩短了集群的暂停时间,基本解决问题

标签:Full,持久,GC,JVM,line,Class,加载
From: https://www.cnblogs.com/chentianjun/p/18113631

相关文章

  • redis自学(28)RDB持久化
    RDBRDB全程RedisDatabaseBackupfile(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件成为RDB文件,默认是保存在当前运行目录。Redis停机时会执行一次RDB。 也就是优......
  • AGC066 题解
    A将网格黑白染色,将黑色格变为\(\bmod2d=0\),白色格变为\(\bmod2d=d\)。这样代价上界为\(n^2d\)。但是这样的“期望代价”是\(\frac{1}{2}n^2d\)的,考虑将黑色格变为\(\bmod2d=x\),白色格变为\(\bmod2d=d+x\),根据鸽巢原理,一定有一种方案代价在\(\frac{1}{2}n^2d......
  • FullStack
    [TOC]#一.前端:##1.Html###1.1常见标签-div/span/a/img/table/form/ul/ol/li/strong/b/i/p/-行内:span/a/b/s/del/em/sup/sub/-块级:div/p/h1/table/tr/th/td/ul/ol/li/dl/-inline-block:img/button/inpu......
  • 【题解】AGC008F | 思维 统计技巧 换根 二次扫描
    题意:给出一个\(n\)个点的树(边权为\(1\))和集合\(S\),求有多少个点集\(T\)可以被表示为离\(S\)中的一个点\(u\)距离不超过\(d\)的点构成的集合(下文称为\(u\)的\(d\)级邻域)。考虑\(S\)为所有点的特殊情况:我们直接求每个点邻域的个数再求和,会算重一些点集,这种情况......
  • AGC008E Next or Nextnext 解题报告
    \(\text{分析}\)\(i\toa_i\)构成内向基环树,配合暴力程序观察内向基环树常见的一些特殊情况:灰色笔对应的是\(i\toa_i\),黑色笔对应的是\(i\top_i\),我们相当于要构造一个黑色的排列(若干环)使得每一条灰色边的起点可以通过一条或两条黑色边到达终点。\(a_i=i\)(全是自环):可以任......
  • 好书推荐 《AIGC重塑金融》
    作者:林建明来源:IT阅读排行榜本文摘编自《AIGC重塑金融:AI大模型驱动的金融变革与实践》,机械工业出版社出版这是最好的时代,也是最坏的时代。尽管大模型技术在金融领域具有巨大的应用潜力,但其应用也面临不容忽视的风险和挑战。本文将深入研究大模型在金融领域的数据隐私......
  • AGC066 题解
    题解:AT_agc066_a[AGC066A]AdjacentDifference笑点解析:没有必要将总成本最小化。我们将格子间隔的黑白染色(显然有两种染色方法),对于黑点我们要求它是奇数倍\(d\),对于白点我们要求它是偶数倍\(d\),这样一定满足相邻格子相差至少\(d\)。因为两种染色方法的代价和为\(dN^2\),......
  • 使用LangChain SQLChain连接LLM和SQL数据库
    大家好,近年来大型语言模型(LLMs)因在多个领域的文本生成能力受到广泛关注。然而,LLMs有时会产生错误或生成无意义的文本,这种现象常被称为“幻觉”。例如,询问ChatGPT法国是什么时候赠送给立陶宛维尔纽斯电视塔的,ChatGPT可能错误地会回答“在1980年”,这与事实不符,因为法国与维尔纽斯......
  • Redis 高可用之持久化
    一.高可用相关知识1.什么是高可用在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量......
  • 谈谈我对 AIGC 趋势下软件工程重塑的理解
    作者:陈鑫今天给大家带来的话题是AIGC趋势下的软件工程重塑。今天这个话题主要分为以下四大部分。第一部分是AI是否已经成为软件研发的必选项;第二部分是AI对于软件研发的挑战及智能化机会,第三部分是企业落地软件研发智能化的策略和路径,第四部分是我们现有的可采纳的、可落......