最近有一个服务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.编写一个接口,模拟第三方的接口,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
效果比较明显,不过连接池的数量,还是需要根据实际场景来设置。
这里发几个还不错的链接:
MAT(Memory Analyzer Tool)下载:
https://www.eclipse.org/mat/
MAT(Memory Analyzer Tool)工具使用超详细版
https://blog.csdn.net/lyd135364/article/details/121449969
线上环境频繁GC问题排查,Finalizer对象该背这个锅吗?
https://blog.csdn.net/libankling2008/article/details/116789217