FGC实战:如何用Idea揪出开源组件调用System.gc导致频繁FGC

栏目: Java · 发布时间: 6年前

内容简介:↓↓↓↓

某天上午收到最近发布的一个服务 频繁FGC 的告警,这个服务只是给公司内部相关人员使用的,并非给互联网用户提供服务的系统。而且功能也比较简单,就是查看一些统计信息、报表数据、数据导出Excel等,访问量非常低。

另外,由于这个服务并不是OLTP,所以就是简单的使用默认的ParallGC垃圾回收器,JVM参数也只是做了简单的配置:

-Xmx512m -Xms512m -Xmn256m XX:+PrintGCDetails  -XX:+PrintGCDateStamps -Xloggc:/log/gc/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/log/dump

关键线索

废话不多说,既然有FGC,那就首先查看GC日志。毕竟FGC的原因还是比较多的,GC日志也是 最直观 的展示FGC原因的证据之一。

这里要补充一句,我们在排查问题时,切记不要靠自己的主观意识猜测,而应该做到每一步的推断都有理有据,这样才能越来越接近真相,找出真正的凶手。

GC日志中寻找FGC的命令以及结果如下所示(只贴出部分):

afeideMacBook-Pro:jvm afei$ grep "full gc" -i gc.log
2019-06-15T09:35:04.168-0800: [Full GC (System.gc()) [PSYoungGen: 6963K->0K(229376K)] [ParOldGen: 8K->6793K(262144K)] 6971K->6793K(491520K), [Metaspace: 5160K->5160K(1056768K)], 0.0085889 secs] [Times: user=0.03 sys=0.01, real=0.01 secs]
2019-06-15T09:40:08.520-0800: [Full GC (System.gc()) [PSYoungGen: 5184K->0K(229376K)] [ParOldGen: 6793K->6090K(262144K)] 11977K->6090K(491520K), [Metaspace: 5168K->5168K(1056768K)], 0.0085573 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
... ...

根据这段GC日志就能非常肯定: 是因为某些地方调用System.gc()触发的FGC

因为这个服务访问亮比较小,并发量并不高。所以,笔者想看看触发FGC的时候,服务被调用了什么接口(如果是并发很高的OLTP,这种方法可能就不是很适用,毕竟每秒TPS成百上千,甚至更大,根本无法确定是哪个接口引起的)。

以上面这段FGC日志,根据其触发的时间点,然后去系统业务日志中查找。很顺利我们就找到了这个时候是调用了一个导出报表数据到Excel并下载的接口。一开始我还有点怀疑会不会是导出数据量很大引起FGC,但是后面走读业务代码,发现导出数据限制最大不超过1000,所以,这个猜想也就被否定了。

重现问题

既然是导出Excel引起的,那么笔者就打算模拟这段代码,试图以最小量的代码看是否能重现这个问题。毕竟: 范围越小,距离真相也就越近

所以,笔者根据这个接口的业务代码,等价的写出了下面这段代码:

public class JxlTest {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            excelGen();
        }
    }

    private static void excelGen() throws Exception {
        WritableWorkbook book = null;
        WritableSheet sheet;
        try {
            book = Workbook.createWorkbook(new File("/Users/litian/afei.xls"));
            sheet = book.createSheet("PageNo.1", 0);
            Label label = new Label(0, 0, "公众号:【阿飞的博客】");
            sheet.addCell(label);
            book.write();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            book.close();
        }
    }
}

配套的JVM参数如下,接下来运行这段代码即可。过程是如此顺利,10次Excel操作,10次FGC:

-XX:+PrintGCDateStamps -XX:+PrintGCDetails
-Xmx512m -Xms512m -Xmn256m
-verbose:gc

之所以用这段JVM参数,是为了希望通过 -verbose:gc 将GC日志直接输出到idea的控制台,而不是将GC日志输出到GC日志文件中,这样更方便定位问题。而且这段JVM参数相比生产环境的JVM参数,堆的大小和比例都是一样的,垃圾回收器也都是默认的。

既然重现问题了,那就相当于问题解决了一半。窃喜,哈哈哈~~~

另辟蹊径

事实上定位这种问题最常见的方法还是借助 btrace 跟踪是哪里调用了System.gc(),毕竟,如果不能像笔者这种场景用一段很小的代码重现问题,笔者接下来介绍的方法可行性就不是很高。但是既然重现了,那么笔者今天就介绍另外一个门槛更低的办法: 借助IDEA强大的搜索功能 。毕竟定位问题不是为了炫技,只要能找出问题的办法,都是好办法。

需要说明的,这段代码笔者是在一个新建的Maven工程中运行的,这段代码只依赖了一个jxl。之所以这么做,是为了笔者接下来介绍的方法能更精确的定位问题所在:

<!-- https://mvnrepository.com/artifact/net.sourceforge.jexcelapi/jxl -->
<dependency>
    <groupId>net.sourceforge.jexcelapi</groupId>
    <artifactId>jxl</artifactId>
    <version>2.6.12</version>
</dependency>

接下来就是笔者介绍的方法:如何通过牛逼的IDEA找出是哪里调用了System.gc()。在搜索触发FGC的元凶之前,需要先下载jxl的源码--这段重现问题源码唯一的Maven依赖。这个下载必不可少,否则IDEA是无法搜到的:

FGC实战:如何用Idea揪出开源组件调用System.gc导致频繁FGC
Download Sources

jxl源码下载完成后,就是打开IDEA强大的搜索了,由于有些同学是Window,有些同学是Mac,快捷键可能不同。所以笔者不介绍打开这个搜索功能的快捷键,而是介绍打开这个搜索功能的路径:

# 这里是Find in Path,而不是Find
Edit->Find->Find in Path 
# 范围选择
Scope->Custom->Project and Libraries

需要再次强调,一定要先通过idea下载jxl的源码,否则 Find in Path 是搜不到项目依赖的jxl中的内容。如下图所示,只能搜到jdk源码中哪些地方调用了System.gc():

FGC实战:如何用Idea揪出开源组件调用System.gc导致频繁FGC
Find in Path

如果jxl源码下载后, Find in Path 中选择范围 Project and Libraries 后搜索 System.gc() 的结果应该如下所示,红色框中就是依赖的开源组件jxl调用System.gc的地方:

FGC实战:如何用Idea揪出开源组件调用System.gc导致频繁FGC
System.gc

现在范围就很小很小了,经过代码排查,我们很快就发现是在调用WritableWorkbook的close()方法时调用了System.gc(),从而触发了FGC。

FGC实战:如何用Idea揪出开源组件调用System.gc导致频繁FGC

很多同学可能会想到通过JVM参数 -XX:+DisableExplicitGC 来屏蔽掉System.gc()的调用,这不是解决问题,而是掩盖问题。而且事实上,这里还有更好的办法,请继续往下看。

解决方案

我们排查jxl中所有调用System.gc()的地方,发现都是下面这种姿势。Bingo,那么我们只需要将workbookSettings中gcDisabled这个字段的值设置为true就不会调用System.gc了:

if (!workbookSettings.getGCDisabled())
{
  System.gc();
}

gcDisabled设置为true非常简单,只需要稍微改造一下 Workbook.createWorkbook() 即可:

WorkbookSettings settings = new WorkbookSettings();
settings.setGCDisabled(true);
book = Workbook.createWorkbook(new File("/Users/litian/afei.xls"), settings);

代码改造后再次执行,再也不会有调用System.gc触发FGC了。美滋滋呀~~

需要说明的是,最后还需要对这段代码进行压测,不断循环调用,看看是否GC异常,或者有堆外内存泄漏的情况(堆外内存只升不降)。幸运的是,没有任何问题。开心!

【阿飞的博客】 公众号二维码

↓↓↓↓

FGC实战:如何用Idea揪出开源组件调用System.gc导致频繁FGC


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Blog Design Solutions

Blog Design Solutions

Richard Rutter、Andy Budd、Simon Collison、Chris J Davis、Michael Heilemann、Phil Sherry、David Powers、John Oxton / friendsofED / 2006-2-16 / USD 39.99

Blogging has moved rapidly from being a craze to become a core feature of the Internetfrom individuals sharing their thoughts with the world via online diaries, through fans talking about their favori......一起来看看 《Blog Design Solutions》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具