一次线上内存泄漏分析

一次线上内存泄漏分析

  • 现象
  • 分析
  • 解决
  • 后记


现象

线上某个服务A在运行一段时间后,内存会逐步涨高,而且不会下降,最终导致OOM。

分析

  1. 一开始只知道OOM了,无日志输出,无dump文件,不确定是什么原因导致的,于是在程序启动时,增加了参数配置,在程序OOM时,生成dump文件到/dump/ 目录:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dump/

  1. 程序正常运行一段时间后,无意外的再次发生OOM,在/dump/目录把xx.hprof文件下载到本地,借用eclipse的MAT
    插件打开。
  2. 这个插件有个好处,在分析dump文件后,会给出可能有问题的对象。

一次线上内存泄漏分析_第1张图片

  1. 顺着提示,分析对象,我们可以看到大量的dubbo泛化调用产生的链接数量有到32万+。

一次线上内存泄漏分析_第2张图片

一次线上内存泄漏分析_第3张图片

  1. 重新捋了一遍泛化调用的代码:
  • 这里有一个很关键点,每一次泛化调用,都会重新new一个ReferenceConfig,这个对象包含着服务提供方链接及注册中心地址,而从dump文件分析来看,这个对象估计在这个方法执行完成之后,没有回收,导致内存一直增长。

一次线上内存泄漏分析_第4张图片

  • 这里的ReferenceConfig是在方法内构建的,讲道理,在方法执行完成后会被回收释放,但现在看来是没有被回收,那就是在哪里被引用了,导致迟迟没法回收?(待继续深入研究)

解决

既然知道原因,目前在客户端加入了缓存,把referenceconfig对象用一个serviceMap缓存下来,只要在获取为null时才去创建。

private val application: ApplicationConfig = run {
        val zookeeperHostName = ApolloConfigReaderUtils.getString("zookeeper.hostName")
        val zookeeperHostPort = ApolloConfigReaderUtils.getString("zookeeper.port")
        val zookeeperFilePath = ApolloConfigReaderUtils.getString("dubbo.registry.file.path")

        val application = ApplicationConfig()
        application.name = "****"
        val registry = RegistryConfig()
        registry.address = """zookeeper://$zookeeperHostName:$zookeeperHostPort"""
        registry.file = zookeeperFilePath
        application.registry = registry
        application
    }

    private val serviceMap = mutableMapOf<String, GenericService>()

    private fun getGenericService(serviceName: String) : GenericService {
        if (serviceMap.get(serviceName) == null) {
            synchronized(this) {
                val reference = ReferenceConfig<GenericService>()
                // 弱类型接口名
                reference.setInterface(serviceName)
                // System.setProperty("dubbo_group", "dev1")
                reference.group = System.getenv("dubbo_group")
                reference.isCheck = false
                // 声明为泛化接口
                reference.isGeneric = true
                reference.application = application
                // 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
                serviceMap.put(serviceName, reference.get())
            }
        }
        return serviceMap.get(serviceName)!!
    }

后记

目前团队需要一名4年工作经验的朋友加入,坐标广州,如果有道友有兴趣的话,欢迎私信了解!!

你可能感兴趣的:(dubbo,dubbo,java,内存泄漏,OOM,MAT)