概述

本周有个同事过来咨询一个比较诡异的gc问题,大概现象是,系统一直在做cms gc,但是老生代一直不降下去,但是执行一次jmap -histo:live之后,也就是主动触发一次full gc之后,通过jstat -gcutil来看老生代一下就降下去了,初看下理论上不太可能,因为full gc也会对old做回收,于是我要同事针对他们的场景写了一个简单的demo出来,然后果然还真能重现,不过他的demo设置的Heap有32G,于是我通过慢慢调整,最终在很小的内存下也能重现出来

Demo

测试代码如下:

正如我上面注释里写的JVM参数,控制新生代200M,老生代300M,老生代使用率达到90%的时候触发CMS GC,大家可以跑跑看,这种情况下会发现不断做CMS GC,但是老生代就是不降下去,但是只要你主动触发一次Full GC,老生代立马就会回收。
当allocateMemory方法执行完之后,期待的结果是gc之后List及里面的byte数组都应该被回收掉,可是事实并不是这样的

初步定位

这段代码非常简单,我翻来覆去地看着这段代码,试图想改变点什么,能让问题出现峰回路转,我不断地控制for循环的次数和每次分配的内存大小,最终我将目标转移到那个ArrayList上,List里有个数组,在add过程中如果发现数组不够了,于是会进行扩容,那扩容就是创建新的数组,将老的对象放到新数组里,那我试想要是不做扩容会不会有问题?于是我开始调整ArrayList的初始化大小,当我调到一定大小,保证在add过程中不会做扩容,问题真出现了反转,居然能正常回收了,比如上面的demo,将数组长度设置为len,那结果就完全不一样了,老生代很快就被回收了
那目标能锁定到数组扩容了

数组扩容

ArrayList里的数组扩容,使用的是System.arrayCopy调用,这是一个native方法,在java层面创建一个新的长度的数组,然后将老数组和新数组都传进去,在native里将老数组里的元素指针拷贝到新数组里,其实做的是浅拷贝,反复看native这块实现,也基本解释不通那个现象,一度怀疑我对GC的理解了,是不是有哪些细节没有注意到。
经过我内存dump分析,发现上面Demo里的List对象确实被回收了,但是List里的数组没有被回收,这个数组里的byte数组都没有被回收

原来是这个鬼

带着百思不得其解的疑惑和我们组同事讨论,看看还有没有其他可能的没考虑到疑惑点,开始也都觉得疑惑,后来传胜突然想到会不会是存在跨代引用的问题,于是回过来仔细再想想每个步骤,好像还真有可能,因为传给System.arrayCopy的新数组是在java层面构建传进来的,在新生代分配的可能性最大,这样再加上拷贝仅仅是浅拷贝,那么老生代里的byte数组因为存在新生代里新数组的引用,那仅仅做CMS GC就不可能回收这些老生代的对象了,因为CMS GC的一个gc root就是新生代里的对象

那何解

至此终于抓出了那个鬼,于是想应对策略,既然这样,只要保证在cms gc回收old之前做一次ygc就能保证新生代里的那个新数组被回收而没有指向老生代那些byte数组,那么这些数组就能正常被cms gc回收了,所以加上-XX:+CMSScavengeBeforeRemark即可解此问题。

一起来学习吧:

PerfMa KO 系列课之 JVM 参数【Memory篇】

实战:OOM 后我如何分析解决的

又抓了一个导致频繁GC的鬼--数组动态扩容的更多相关文章

  1. 频繁GC会造成卡顿

    频繁GC会造成卡顿 https://www.cnblogs.com/qcloud1001/p/9525078.html 一款app除了要有令人惊叹的功能和令人发指交互之外,在性能上也应该追求丝滑的要求 ...

  2. 面试被问怎么排查平时遇到的系统CPU飙高和频繁GC,该怎么回答?

    处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题.当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警.本文主要针对系统运行缓慢这 ...

  3. 生产环境碰到系统CPU飙高和频繁GC系统反应慢,你要怎么排查?(转)

    处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题.当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警.本文主要针对系统运行缓慢这 ...

  4. CPU飙高,频繁GC,怎么排查?

    处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题.当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警. 本文主要针对系统运行缓慢 ...

  5. 平时碰到系统CPU飙高和频繁GC,你会怎么排查?

    处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题.当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警.本文主要针对系统运行缓慢这 ...

  6. FGC频繁 GC卡顿

    https://mp.weixin.qq.com/s/I1fp89Ib2Na1-vjmjSpsjQ 线上服务的FGC问题排查,看这篇就够了! 原创 骆俊武 IT人的职场进阶 2020-05-10   ...

  7. 故障重现(内存篇2),JAVA内存不足导致频繁回收和swap引起的性能问题

    背景起因: 记起以前的另一次也是关于内存的调优分享下   有个系统平时运行非常稳定运行(没经历过大并发考验),然而在一次活动后,人数并发一上来后,系统开始卡. 我按经验开始调优,在每个关键步骤的加入如 ...

  8. JS垃圾回收——和其他语言一样,JavaScript 的 GC 策略也无法避免一个问题:GC 时,停止响应其他操作,这是为了安全考虑

    JavaScript 内存管理 & 垃圾回收机制 标记清除 js 中最常用的垃圾回收方式就是标记清除.当变量进入环境时,例如,在函数中声明一个变量,就将这个而变量标记为“进入环境”.从逻辑上讲 ...

  9. ########django-基于中间件写一个限制频繁登陆########

    django-基于中间件写一个限制频繁登陆 额额,标题已经很醒目了,通过中间件去实现,其他方法也可以实现 浏览器前端传来的请求,必须通过中间件,才能到后面路由,视图函数,所以我们在中间件那里做一层处理 ...

随机推荐

  1. localStorage和cookie的跨域解决方案

    原文转自:点我 前言 localStorage和cookie大家都用过,我前面也有文章介绍过,跨域大家也都了解,我前面也有文章详细描述过.但是localStorage和cookie的跨域问题,好多小伙 ...

  2. 题解 CF1286A 【Garland】

    updata on 2020.3.19 往博客园搬的时候看了看自己以前写的blog 其实没多久,才两个多月,感觉自己之前写的东西好罗嗦啊.. 但也是最近写的blog才开始多起来 当然现在也没好到哪去. ...

  3. 【K8S】K8S 1.18.2安装dashboard(基于kubernetes-dashboard 2.0.0版本)

    [K8S]K8S 1.18.2安装dashboard(基于kubernetes-dashboard 2.0.0版本) 写在前面 K8S集群部署成功了,如何对集群进行可视化管理呢?别着急,接下来,我们一 ...

  4. java基础篇 之 再探内部类跟final

    之前写过一篇文章:从垃圾回收机制解析为什么局部内部类只能访问final修饰的局部变量以及为什么加final能解决问题,经过这两天的学习,发现有些不对,必须再来捋一捋 先看之前的例子: /** * @a ...

  5. 【Hadoop离线基础总结】MapReduce增强(下)

    MapReduce增强(下) MapTask运行机制详解以及MapTask的并行度 MapTask运行流程 第一步:读取数据组件InputFormat(默认TextInputFormat)会通过get ...

  6. A*启发式搜索

    A*启发式搜索 其实是两种搜索方法的合成( A*搜索算法 + 启发式搜索),但要真正理解A*搜索算法,还是得先从启发式搜索算法谈起. 何为启发式搜索 启发式搜索算法有点像广度优先搜索,不同的是,它会优 ...

  7. Ubuntu 18.04 新系统 允许root远程登录

    1. 查看ssh服务器是否安装并启动 #sudo ps -e | grep ssh 1. 安装ssh服务器 #sudo apt-get install openssh-server 2. 配置sshd ...

  8. web概念简述,HTML学习笔记

    今日内容 1. web概念概述 2. HTML web概念概述 * JavaWeb: * 使用Java语言开发基于互联网的项目 * 软件架构: 1. C/S: Client/Server 客户端/服务 ...

  9. 3D三栅极晶体管(摘抄)

    英特尔的科学家们在2002年发明了三栅极晶体管——这是根据栅极有三面而取名的. 传统“扁平的”2D平面栅极被超级纤薄的.从硅基体垂直竖起的3D硅鳍状物所代替.电流控制是通过在鳍状物三面的每一面安装一个 ...

  10. [hdu4628 Pieces]二进制子状态,DP

    题意:给一个长度为16的字符串,每次从里面删掉一个回文序列,求最少需要几次才能删掉所有字符 思路:二进制表示每个字符的状态,那么从1个状态到另一个状态有两种转移方式,一是枚举所有合法的回文子序列,判断 ...