• 记一次频繁GC问题的排查


    前言

    最近有一个服务gc频繁,这个服务是调用多个三方平台,返回的数据没有大对象,再将数据返回,基本没有多余逻辑。

    分析

    拿到dump日志之后,使用mat工具分析一波,发现都是 java.lang.ref.Finalizer 对象,这个对象没怎么见过,搜索资料之后,才知道这是实现了finalize() 方法的对象会被标记成这个类,然后有 finalizerThread 单独处理他们。

    搜索的时候搜到了一篇好文:《线上环境频繁GC问题排查,Finalizer对象该背这个锅吗?》,和我们项目场景很像,使用文中的mat方法观察后,也是有很多 HttpsURLConnectionImpl 、 ZipFile$ZipFileInflaterInputStream 对象等。
    那么原因初步找到,引用文中的话,就是每次请求都会 new HttpURLConnection 对象,请求完毕 该对象及其关联的对象并没有立即释放内存而是进入了ReferenceQueue队列等待Finalizer 的守护线程来回收。如果主线程的请求比较频繁,就会产生大量的Finalizer对象放入到Queue中。而守护线程的优先级是比较低导致回收Finalizer对象的速度远低于主线程产生的速度,这样就导致了eden区内存迅速的被Finalizer对象占满。
    我们请求第三方服务用的是 hutool 的 HttpUtil,在execute() 方法中,会每次创建连接:

    /**
     * 初始化网络连接
     */
    private void initConnecton() {
    	this.httpConnection = HttpConnection.create(URLUtil.toUrlForHttp(this.url, this.urlHandler), this.proxy)//
    			.setMethod(this.method)//
    			.setHttpsInfo(this.hostnameVerifier, this.ssf)//
    			.setConnectTimeout(this.connectionTimeout)//
    			.setReadTimeout(this.readTimeout)//
    			// 自定义Cookie
    			.setCookie(this.cookie)
    			// 定义转发
    			.setInstanceFollowRedirects(this.maxRedirectCount > 0 ? true : false)
    			// 覆盖默认Header
    			.header(this.headers, true);
    
    	// 是否禁用缓存
    	if (this.isDisableCache) {
    		this.httpConnection.disableCache();
    	}
    }
    // HttpConnection.create 方法如下:
    /**
     * 创建HttpConnection
     * 
     * @param url URL
     * @param proxy 代理,无代理传{@code null}
     * @return HttpConnection
     */
    public static HttpConnection create(URL url, Proxy proxy) {
    	return new HttpConnection(url, proxy);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    验证

    首先不改代码,进行压测:
    1.编写一个接口,模拟第三方的接口,sleep 300ms返回
    2.服务启动时,JVM启动参数设置最大内存:-Xmx512m
    3.使用JMeter设置100线程,压测时间设置为永远
    4.等待压测10分钟,使用jps和jmap,将dump日志输出出来
    5.接着修改代码,将 hutool 的 HttpUtil 换成 OKHttp ,并且设置连接池为5
    6.对修改的服务进行压测,输出10分钟的dump文件

    压测结果对比

    修改代码前,Finalizer对象占用了84,762,136 (51.50%) bytes

    修改代码后,Finalizer对象占用了6,331,344 (7.38%) bytes

    效果比较明显,不过连接池的数量,还是需要根据实际场景来设置。

    附录

    这里发几个还不错的链接:

    1. MAT(Memory Analyzer Tool)下载:
      https://www.eclipse.org/mat/

    2. MAT(Memory Analyzer Tool)工具使用超详细版
      https://blog.csdn.net/lyd135364/article/details/121449969

    3. 线上环境频繁GC问题排查,Finalizer对象该背这个锅吗?
      https://blog.csdn.net/libankling2008/article/details/116789217

  • 相关阅读:
    深度学习【fastText原理解析】
    C/C++中注释方式以及规范
    研二学妹面试字节,竟倒在了ThreadLocal上,这是不要应届生还是不要女生啊?
    “软硬结合”- 转转搜索少无结果模块简介
    网络编程套接字socket
    java计算机毕业设计在线教育系统源代码+系统+数据库+lw文档
    EN 13859-2防水用柔性薄板—CE认证
    Unity学习笔记--详细介绍CacheServer、部署方法、以及在Unity中的位置
    Electron进程通信的另一种方式
    对于Java循环中的For和For-each,哪个更快
  • 原文地址:https://blog.csdn.net/xcy1193068639/article/details/126692463