线上环境频繁GC问题排查,Finalizer对象该背这个锅吗?

1 篇文章 0 订阅

问题描述

公司的一个SpringMVC服务,最近在做运维检查的时候发现young gc 和 full gc太频繁,远远超过了正常情况。服务器配置是4核8G,该服务分配了6G内存。通过arthas的dashboard统计情况在20个小时之内发生的young gc和 full gc 次数,如下图:young gc 393次,full gc 19次
在这里插入图片描述

问题排查

  1. 在eden区达到80%的时候,通过arthas 的 heapdump dupm了堆内存文件
java -jar arthas-boot.jar
挂载对应的应用的PID,进入到arthas管理界面
heapdump /tmp/dump.hprof
  1. 使用MemoryAnalyzer 分析hprof 文件,发现heap中存在最多的对象就是java.lang.ref.Finalizer 对象,占用了整个空间的47%,如下图
    在这里插入图片描述

  2. 点击Histogram 查看Finalizer对象,右键—>List Objects—> with outgoing reference 查看具体的Finalizer对象,查看图片左下角的referent 就是真正需要回收的垃圾。分析了一部分referent 都是涉及到底层jdk 关于https请求连接的对象。如HtppsURLConnectionIpml,ZipFileInputStream等。
    在这里插入图片描述
    在这里插入图片描述

  3. 结合线上系统的业务日志发现该服务与第三方系统交互频繁,就是通过原生的jdk 自带的HttpsURLConnection 来发起的请求。那具体是什么原因造成了大量的Finalizer对象堆积呢?

  4. 原因就是每次发起请求都会 new HttpURLConnection 对象,请求完毕 该对象及其关联的对象并没有立即释放内存而是进入了ReferenceQueue队列等待Finalizer 的守护线程来回收。如果主线程的请求比较频繁,就会产生大量的Finalizer对象放入到Queue中。而守护线程的优先级是比较低导致回收Finalizer对象的速度远低于主线程产生的速度,这样就导致了eden区内存迅速的被Finalizer对象占满。

  5. 为什么这些对象不是直接回收而是进入到Queue等待被守护线程回收呢?那是因为这些对象重写了finalize()方法,导致需要释放的对象被放入到Queue通过守护线程来回收。

HttpsURLConnectionImpl.java 
protected void finalize() throws Throwable {
    this.delegate.dispose();
}
protected void dispose() throws Throwable {
    super.finalize();
}

解决方案

解决方案就是减少Finalizer对象的产生速度,但是业务访问是不能减少,所以我们就引入了HttpClient 现有的连接池功能,不用每次连接都new 新的对象而是复用连接池中空闲的对象。封装上线后频繁young gc的问题得到了彻底的解决。事后监控基本上几个小时发生一次young gc ,full gc更少了。

总结

  1. 线上环境需要一定的成熟的APM(应用性能管理) 对企业系统即时监控以实现对应用程序性能管理和故障管理的系统化的解决方案。
  2. 通过合理搭配工具,提高问题处理能力。
  3. 经过线上实际问题的处理,加深了对系统底层的了解,对以后问题排查流程更加熟练、更加自信、更加高效。
  4. 实践是检验巩固技术的唯一真理–诚不欺我。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值