资讯专栏INFORMATION COLUMN

通过btrace排查线上频繁Full GC的case

mochixuan / 820人阅读

摘要:概述又是一次因为线上报警机制开启的排查问题之旅。的常见使用场景有分析哪些方法调用,获取其调用栈接口性能差,分析耗时情况当出现异常时,分析方法的运行时参数线上有一个大对象,查看其内容安装使用依赖于,首先要安装好并配置的环境变量。

概述

又是一次因为线上报警机制开启的排查问题之旅。某日,钉钉机器人疯狂报警:

接着就是申请机器权限去排查问题,既然是频繁Full GC,那我们排查问题的思路就应该是找到引起Full GC的原因。引起频繁Full GC的常见原因有这么几点:

堆外内存达到阈值,将会调用System.gc()来做一次Full GC。这里说的堆外内存主要说的是nio下的DirectByteBuffer,它会通过Unsafe接口通过os::malloc来分配内存,然后将内存的起始地址和大小存到DirectByteBuffer对象中,只有当DirectByteBuffer被回收掉之后堆外内存才可能被回收。具体堆外内存回收的细节大家可以看下笨神的JVM源码分析之堆外内存完全解读.

项目中显示或隐式的调用了System.gc()。System.gc的详情可见链接描述

老年代可用空间不够了,导致它的原因有多种,但是常见的排查思路通过mat去分析堆内存dump文件。

定位Full GC的原因

gc日志永远都是我们排查gc问题最好的工具,所以强烈建议大家在线上配置-XX:+PrintGCDetails -Xloggc:/data/logs/gc.log方便我们去定位问题。由于当前项目没有配置,只好用jstat -gccause去监测,博主监测了五分钟左右,得到如下信息:新生代、老年代、元空间内存还很多,FGC涨了9次,并且对应的LGCC都显示为System.gc.由于虚拟机参数并没有去配置-XX:MaxDirectMemorySize,所以其堆外内存受限于当前物理机内存,故我们可以通过top去查看进程占了多少内存和通过free -m查看空闲内存(当然visualvm的插件和perftools都是查看堆外内存使用情况很好的工具,不了解自行谷歌)。经过内存分析和业务分析之后,初步断定不是堆外内存导致的,而是由项目中有调用System.gc().

如何查找项目哪里有调用Full GC呢

首先对项目全局进行搜索System.gc(),如果没有查到,那么就很可能是依赖的jar包里存在调用,如何在jar包中查找呢?在这里给大家推荐一款插件Btracegithub地址。BTrace是Java的安全可靠的动态跟踪工具。 他的工作原理是通过 instrument + asm 来对正在运行的java程序中的class类进行动态增强。说他是安全可靠的,是因为它对正在运行的程序是只读的。也就是说,他可以插入跟踪语句来检测和分析运行中的程序,不允许对其进行修改。因此他存在一些限制:

不能创建对象

不能创建数组

不能抛出和捕获异常

不能调用任何对象方法和静态方法

不能给目标程序中的类静态属性和对象的属性进行赋值

不能有外部、内部和嵌套类

不能有同步块和同步方法

不能有循环(for, while, do..while)

不能继承任何的类

不能实现接口

不能包含assert断言语句

根据官方声明,不恰当的使用Btrace会导致jvm崩溃,所以在上生产环境之前,一定要在本地充分验证脚本的正确性。Btrace的常见使用场景有:

分析哪些方法调用System.gc,获取其调用栈

接口性能差,分析耗时情况

当出现异常时,分析方法的运行时参数

线上有一个大对象ArrayList,查看其内容

安装使用Btrace

Btrace依赖于JDK,首先要安装好JDK并配置JDK的环境变量。

1.下载安装包

下载地址:https://github.com/btraceio/btrace/releases/tag/v1.3.11

2.解压缩


修改目录权限:

3.配置环境变量

然后source更新环境变量
4.编写btrace脚本

import static com.sun.btrace.BTraceUtils.jstack;
import static com.sun.btrace.BTraceUtils.println;
import static com.sun.btrace.BTraceUtils.str;
import static com.sun.btrace.BTraceUtils.strcat;
import static com.sun.btrace.BTraceUtils.timeMillis;

import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.TLS;
@BTrace
public class MyTest {
    @OnMethod(clazz = "java.lang.System", method = "gc" )
    public static void startMethod(){
        println("****************************************");
        jstack();
        println("****************************************");
    }
    @OnMethod(clazz = "java.lang.System", method = "gc", location = @Location(Kind.RETURN))
    public static void endMethod(){
        println("=========================================");
        jstack();
        println("=========================================");
    }
}

5.运行btrace

通过jps -l获得进程的pid,然后通过 btrace >> gc.txt & 以后台任务的方式去开启btrace脚本监听对应进程,将对应的栈信息保存到gc.txt中。
6.关闭btrace

经过长达一天的监控,终于抓到了对应的调用栈:


原因是项目中用到了jxl的Workbook来做Excel相关的功能,每次关闭Workbook的时候都会调用System.gc

至此,我们就已经抓住了导致频繁Full GC的鬼了,改动方法也特别简单,在构造WorkBook的时候,将其成员变量WorkbookSettings的成员变量gcDisabled设置为true即可避免此问题,或者添加vm参数-Djxl.nogc=true(不太推荐).

总结

每次排查问题的时候遇到了很多困难,在总结的时候却又不知道该说些什么~.~。特别感谢提供帮助的笨神、阿飞和零度。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69357.html

相关文章

  • CMS垃圾回收和线上Full GC排查

    摘要:是一款基于并发使用标记清除算法的垃圾回收算法,只针对老年代进行垃圾回收。收集器工作时,工作线程和用户线程可以并发执行,以达到降低时间的目的。并发清理清理垃圾对象,这个阶段线程和用户线程并发执行。 背景 我们上线Java服务的时候需要对其配置一些JVM参数,如堆空间大小、虚拟机栈大小、垃圾回收算法。对于年轻代和老年代我们可以配置不同的垃圾回收算法。在一些对rt要求很高的场景,服务不能有长...

    zr_hebo 评论0 收藏0
  • 记一次单个服务将cpu占满问题排查

    摘要:一发生故障今天发现服务查询一直卡住,就看了一下服务器当时就愣住了就这一个服务就把占满了,再看了下端口号出现了大量的。 一、发生故障 今天发现服务查询一直卡住,就看了一下服务器: showImg(https://segmentfault.com/img/bVbrL5i?w=842&h=57); 当时就愣住了就这一个服务就把CPU占满了,再看了下端口号: showImg(https://s...

    BetaRabbit 评论0 收藏0
  • Javag工程师成神之路(2019正式版)

    摘要:结构型模式适配器模式桥接模式装饰模式组合模式外观模式享元模式代理模式。行为型模式模版方法模式命令模式迭代器模式观察者模式中介者模式备忘录模式解释器模式模式状态模式策略模式职责链模式责任链模式访问者模式。 主要版本 更新时间 备注 v1.0 2015-08-01 首次发布 v1.1 2018-03-12 增加新技术知识、完善知识体系 v2.0 2019-02-19 结构...

    Olivia 评论0 收藏0
  • 记一次线上频繁FGC事件和解决方式

    摘要:直接显示了一个疑似内存泄漏的问题。然后分析文件给出的信息,发现一个叫的类。文件里面说的内存泄漏的大概的意思就是说,这个类里面的存放的东西太多了,爆掉了。修改了代码将调用的地方改成了单例。修改完线上跑了一段日子,后来也没有出现过这样的问题。 问题描述:     早上去公司上班,突然就邮件一直报警,接口报异常,然后去查服务器的运行情况,发现java的cpu爆了.接着就开始排查问题 问题解决...

    Alliot 评论0 收藏0

发表评论

0条评论

mochixuan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<