又是一个程序员粗心的代码引起频繁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 something
-
executor.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-p91782
查看进程里的线程情况:
-
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-l91782>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-gcutil917825s
查看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.281
-
0.00 0.00 100.00 99.98 78.57 83.36 5 0.633 371 331.965 332.598
-
0.00 0.00 100.00 99.98 78.57 83.36 5 0.633 376 336.996 337.629
-
0.00 0.00 100.00 99.98 78.57 83.36 5 0.633 381 340.795 341.428
-
0.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.bin91782
,用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 something
-
executor.scheduleWithFixedDelay(() -> {
-
try{
-
doFutureContract(contract);
-
}catch (Exception e){
-
e.printStackTrace();
-
}
-
}, 2, 3, TimeUnit.SECONDS);
-
});
-
}
那么为什么会出现这种情况呢?我相信一个程序员不应该犯这样的低级错误,后来看到原生代码,我做出一个比较合理的猜测,其本意可能是想通过调用 Executorexecutor
来异步执行,谁知小手一抖,在红色框那里输入了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 something
-
executor.execute(() -> {
-
try{
-
doFutureContract(contract);
-
}catch (Exception e){
-
e.printStackTrace();
-
}
-
});
-
});
-
}
或者将这一块直接改成同步处理,不需要线程池:
-
private static void buildBar(){
-
List<FutureContract> futureContractList = getAllFutureContract();
-
futureContractList.forEach(contract -> {
-
// do something
-
try{
-
doFutureContract(contract);
-
}catch (Exception e){
-
e.printStackTrace();
-
}
-
});
-
}
原文发布时间为: 2018-11-08
本文作者: Java技术驿站
本文来自云栖社区合作伙伴“Java技术驿站
”,了解相关信息可以关注“
Java技术驿站
”。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
使用Visual Studio Code开发.NET Core看这篇就够了
原文: 使用Visual Studio Code开发.NET Core看这篇就够了 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9926078.html 在本文中,我将带着大家一步一步的通过图文的形式来演示如何在Visual Studio Code中进行.NET Core程序的开发,测试以及调试。尽管Visual Studio Code的部分功能还达不到Visual Studio的水平,但它实际上已经足够强大来满足我们的日常开发。而且其轻量化,插件化以及跨平台的特性则是VS所不具备的。而且Visual Studio Code还可以通过社区来创建一系列的扩展来增强其功能,且社区已经足够活跃。我们可以期待更多很酷的扩展和功能来增强VS Code,这将使在这个轻量级,跨平台编辑器中的开发.NET Core应用程序更加流畅和有趣。赶紧跟着博主一起开始今天的文章吧! 为什么要写这篇文章? 因为上篇文章也说了,.NET Core已经全面跨平台了,而且我们也在尝试使用Linux了,但是上篇CentOS开发ASP.NET Core入门教程 中使用的CLI...
- 下一篇
java8学习:引入stream
内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。书籍购买地址:java8实战 这一篇内容要讲的是流,关于流的概念,流的内部迭代跟外部迭代的区别之类的偏概念性的问题,下一篇在细讲流到底是什么用 那么说到流,我们自然就会想到水,这就涉及到水源头是哪里,水怎么流过来的,水会被我们的桶收集起来,还是说用水杯收集起来,那么问题就一下子出现了三个 流的源头是哪里 流的源头包括集合,数组或者一些输入输出资源,但是需要注意的是,从有序集合生成流时会保持原有的数据,从列表生成的流,其元素数据与列表一致 流是怎么个处理过程 这其实就要设计到用到了具体的api了,下面会说到 流的收集 无非就是收集成集合等一些等存储数据的数据结构中 流和集合作对比 一点很清楚的就是,流是来做计算的,而集合是用来存储的,这个应该很容易理解 还有一个就是:我们需要看一部电影,那么无非就是两种在线看和下载下来看,那么在线看就属于流模式,而整个资源下载下来看就属于集合模式了,所以从简单的说集合与流之间的差异就在与什么时候计算。集合是一个内存中的数据结构,它包含了...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程