这是笨神JVMPocket群里一位名为”云何*住“的同学提出来的问题,问题现象是CPU飙高并且频繁FullGC

重现问题


这位同学的业务代码比较复杂,为了简化业务场景,笔者将其代码压缩成如下的代码片段:

public class FullGCDemo {private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws Exception {executor.setMaximumPoolSize(50);// 模拟xxl-job 100ms 调用一次, 原代码没有这么频繁for (int i=0; i<Integer.MAX_VALUE; i++){buildBar();Thread.sleep(100);}}private static void buildBar(){List<FutureContract> futureContractList = getAllFutureContract();futureContractList.forEach(contract -> {// do somethingexecutor.scheduleWithFixedDelay(() -> {try{doFutureContract(contract);}catch (Exception e){e.printStackTrace();}}, 2, 3, TimeUnit.SECONDS);});}private static void doFutureContract(FutureContract contract){// do something with futureContract}private static List<FutureContract> getAllFutureContract(){List<FutureContract> futureContractList = new ArrayList<>();// 问题代码这里每次只会new不到10个对象, 我这里new了100个是为了更快重现问题for (int i = 0; i < 100; i++) {FutureContract contract = new FutureContract(i, ... ...);futureContractList.add(contract);}return futureContractList;}
}

说明,为了更好的还原问题,FutureContract.java 的定义建议尽量与问题代码保持一致:

  • 16个BigDecimal类型属性
  • 3个Long类型属性
  • 3个String类型属性
  • 4个Integer类型属性
  • 2个Date类型属性

问题代码运行时的JVM参数如下(JDK8):

java -Xmx256m -Xms256m -Xmn64m FullGCDemo

你也可以先自己独立思考一下这块代码问题何在。

CPU飙高


这是第一个现象,top命令就能看到,找到我们的进程ID,例如91782。然后执行命令top -H -p 91782查看进程里的线程情况:

PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
91784 yyapp     20   0 2670m 300m  12m R 92.2  7.8   4:14.39 java
91785 yyapp     20   0 2670m 300m  12m R 91.9  7.8   4:14.32 java
91794 yyapp     20   0 2670m 300m  12m S  1.0  7.8   0:09.38 java
91799 yyapp     20   0 2670m 300m  12m S  1.0  7.8   0:09.39 java

由这段结果可知线程91784和91785很消耗CPU。将91784和91785分别转为16进制,得到16688和16689。接下来通过执行命令命令jstack -l 91782 > 91782.log导出线程栈信息(命令中是进程ID),并在线程dump文件中寻找16进制数16688和16689,得到如下两条信息:

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f700001e000 nid=0x16688 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f7000020000 nid=0x16689 runnable

由这两行结果可知,消耗CPU的是ParallelGC线程。因为问题代码搭配的JVM参数没有指定任何垃圾回收器,所以用的是默认的PS垃圾回收,所以这个JVM实例应该在频繁FullGC,通过命令jstat -gcutil 91782 5s查看GC表现可以验证,由这段结果可知,Eden和Old都占满了,且不再发生YGC,但是却在频繁FGC,此时的应用已经不能处理任务,相当于假死了,好可怕:

S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT 0.00   0.00 100.00  99.98  78.57  83.36      5    0.633   366  327.647  328.2810.00   0.00 100.00  99.98  78.57  83.36      5    0.633   371  331.965  332.5980.00   0.00 100.00  99.98  78.57  83.36      5    0.633   376  336.996  337.6290.00   0.00 100.00  99.98  78.57  83.36      5    0.633   381  340.795  341.4280.00   0.00 100.00  99.98  78.57  83.36      5    0.633   387  346.268  346.901

揪出真凶


到这里基本可以确认是有对象没有释放导致即使发生FullGC也回收不了引起的,准备dump进行分析看看Old区都是些什么妖魔鬼怪,执行命令jmap -dump:format=b,file=91782.bin 91782,用MAT分析时,强烈建议开启keep unreachable objects

接下来点击Actions下的Histogram,查找大对象:

下面贴出的是原图,而不是笔者的Demo代码跑出来的:

由这段代码可知,大量的FutureContract和BigDecimal(说明:因为FutureContract中有多达16个BigDecimal类型的属性),FutureContract占了120MB,BigDecimal占了95MB。那么就可以断定问题是与FutureContract相关的代码造成的,如果是正常的JVM示例,Histogram 试图最占内存的是byte[]和char[]两个数组,两者合计一般会占去80%左右的内存,远远超过其他对象占用的内存。

接下来通过FutureContract就找到上面这块buildBar方法代码,那么为什么是这块代码无法释放呢?单独把这块代码拧出来看看,这里用到了ScheduledThreadPoolExecutor定时调度,且每3秒执行一次,然而定时器中需要的参数来自外面的List<FutureContract>,这就会导致List<FutureContract>这个对象一直被一个定时任务引用,永远无法回收,从而导致FutureContract不断晋升到Old区,直到占满Old区然后频繁FullGC。

private static void buildBar(){List<FutureContract> futureContractList = getAllFutureContract();futureContractList.forEach(contract -> {// do somethingexecutor.scheduleWithFixedDelay(() -> {try{doFutureContract(contract);}catch (Exception e){e.printStackTrace();}}, 2, 3, TimeUnit.SECONDS);});
}

那么为什么会出现这种情况呢?我相信一个程序员不应该犯这样的低级错误,后来看到原生代码,我做出一个比较合理的猜测,其本意可能是想通过调用Executor executor来异步执行,谁知小手一抖,在红色框那里输入了taskExecutor,而不是executor:

解决问题


OK,知道问题的根因,想解决问题就比较简单了,将taskExecutor改成executor即可:

private static ThreadPoolExecutor executor = new ThreadPoolExecutor(50, 50, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(128));
private static void buildBar(){List<FutureContract> futureContractList = getAllFutureContract();futureContractList.forEach(contract -> {// do somethingexecutor.execute(() -> {try{doFutureContract(contract);}catch (Exception e){e.printStackTrace();}});});
}

或者将这一块直接改成同步处理,不需要线程池:

private static void buildBar(){List<FutureContract> futureContractList = getAllFutureContract();futureContractList.forEach(contract -> {// do somethingtry{doFutureContract(contract);}catch (Exception e){e.printStackTrace();}});
}

又是一个程序员粗心的代码引起频繁FullGC的案例相关推荐

  1. 【人物】一个程序员决定写代码到60岁

    重要也更深远的意义或许在于,阳振坤的存在创造了一种新的生态.他颠覆了时下困扰这个行业的「35岁危机」:一个程序员必须在35岁之前跻身进入管理层,否则将无法以技术身份立足,年长的技术人员意味着贬值.淘汰 ...

  2. 一个程序员老人用代码写的临终遗言

    定义多个变量最后输出I miss you./n 先翻译代码字面意思:两个世界,无言的信,短暂停留长长的会回忆--..想念完还换行了 感性程序员的翻译:阴阳两隔,想写信烧纸沟通又不认识鬼文,爱人死得早, ...

  3. 【程序人生】一个程序员不敲代码改写专栏了,三个月收入……

    ❤️欢迎订阅专栏<程序人生>,分享职业路线,职场干货.仅前50名免费❤️ 道阻且长,行则将至 前言 哈喽,大家好,我是一条. 2021-04-24,我发布了这篇<是的,我离职了!&g ...

  4. 作为一个程序员,敲代码还是二指禅??? 教你几招,让你打字速度飞起

    同一个人在不同的打字环境下,速度是不同的.打字环境包括三种:看打.想打.听打.所以,在追求打字速度的时候,要先确认自己是在什么样的打字环境下打字.因为这会决定什么样的输入法适合你.工欲善其事,必先利其 ...

  5. 从前有一个程序员,成天写代码,后来。。。

    之前这里是网易云音乐 的外链,许巍演唱版本的<执着>,我们搞IT 的多少都有些执着,后来网页没有了版权,我只好放上来歌词了. 如果对文章没有太多兴趣,可以只读一遍歌词,或者听一遍歌,哈哈. ...

  6. 当一个程序员表情夸张,双手在键盘上疯狂敲击,你真的以为是在敲代码!!?

    分享一个有关程序员的有趣现象: 当一个程序员表情夸张,双手在键盘上疯狂敲击,你真的以为是在敲代码!!? NONONO,当有这种动作的时候,这个程序员7成的概率在水群.论坛吹牛: 2成的概率给予一些技术 ...

  7. 流浪地球的HTML代码,《流浪地球》:一个程序员用代码拯救了世界

    原标题:<流浪地球>:一个程序员用代码拯救了世界 要说春节档最大的话题电影不是宁浩黄渤.不是韩寒沈腾.甚至不是星爷宝强,而是科幻片<流浪地球>! 而在看了这部电影的网友都在感叹 ...

  8. 在华为写了十几年代码,一个程序员的自我修养

    以下内容来自公众号逆锋起笔,关注每日干货及时送达 本文来自<华为人>徐宏伟,转载请注明出处 一天晚上,我和老婆聊天,说部门要我写个"大咖谈软件"的文章,老婆斜了我一眼, ...

  9. 如何写出扩展性高、维护性好的代码?(一个程序员最基本的修养)

    我们要解决什么问题? 以下是我们在实际工作中常常会遇到的问题.搞明白了以下的问题,此文就算学会了.掌握了此文的知识,你的代码水平也将提高一个台阶. 1 实际工作中看到同事写的代码是不是很抓狂?别人看到 ...

最新文章

  1. pytorch中调整学习率的lr_scheduler机制
  2. jenkins 修改工作目录
  3. linux u盘分区 mdev 卸载问题,嵌入式linux 实现mdev SD卡和U盘自动挂载和卸载的方法 mdev.conf...
  4. iOS微博项目(七)发微博和定位
  5. python基础教程运行程序_Python入门基础教程:WSGI
  6. centos7.3二进制安装mariadb10.2.8
  7. “吃神么,买神么”的第一个Sprint计划(第三天)
  8. 多元函数微分学的几何应用
  9. 实用插件(七)视频播放插件——ckplayer
  10. js距离单位换算_1等于多少公里
  11. 中国房价走势分析——基础数据收集
  12. ICPR 2018|阿里巴巴读光OCR及MTWI数据集亮相引关注
  13. QT QPushbutton制作下拉列表
  14. 2022春招前端实习面经【美团、钉钉、快手、字节、招行、百度、拼多多、腾讯】(未完)
  15. 基于AIE平台的决策树算法的黔东南州水稻提取
  16. dnsmasq mysql_OpenStack Ironic之inspect自检
  17. 机器视觉光源的设计方法
  18. linux打地鼠课程设计,数字电路课程设计打地鼠.doc
  19. 医院检验科设计需要注意什么
  20. 用python-OpenCV做一个魔方墙找茬程序(3D视眼训练) 版本2.0:加入倒计时功能

热门文章

  1. nginx 如何处理请求系列1-Nginx安装
  2. 怎样将outlook express中的邮件保留在原邮箱
  3. 在没有工具的情况下检查SSD 的TRIM功能有没有打开
  4. 使用元数据设计测试用例
  5. 使用各种方法加速大型矩阵运算的效率对比
  6. linux环境下创建MyOS虚拟机
  7. 加油!兄弟连的兄弟们!
  8. 众望所归的《JAVASCRIPT凌厉开发--EXT详解与实践 》终于上市了!
  9. 【C/C++】运算结果出现1.#Q0问题分析
  10. Mathematica基础——Part——[[]]