一、背景

公司有一个中间的系统A可以对接多个后端业务系统B,一个业务系统以一个Namespace代表, Namespace中包含多个FrameChannel(用holder保存),表示A连接到业务系统B各服务实例的连接;A与B通过GRPC通信。
 

二、现象

测试使用一台服务实例A,对应后端的一个业务系统B,该业务系统有两台服务实例,正常情况NameSpace中包含两个FrameChannel

 
当后端业务系统升级上线重启时,会重新创建FrameChannel,但旧的FrameChannel在GC(自己创建大量client,发送埋点消息,并使用jstat观察gc数量,过程不详述了)时却没有被释放,正常情况下,FrameChannel数量为2,当pp重启后,FrameChannel的数量变成4,并在gc时,没有被释放。

 
正常情况Framechannel有2个,即两条线,当重启pp时,会变成4条线,查看堆内存FrameChannel对象,也是4个

既然仍能监控旧的FrameChannel,于是想到将旧的FrameChannel注销监控

 
再重新将A部署测试,发现当重启pp时,另外两个FrameChannel确实没有数据了,但堆内存中却仍然有4个FrameChannel对象(原因分析见下面的分析部分)

 

 
最后分析堆内存后,发现注销指标时少注销了一部分,重新开发,编译,打包,部署,并测试

 
发现FrameChannel对象仍然为4个,再分析堆内存,发现被Session引用,于是关闭所有client,再观察一会,FrameChannel数量终于变成了2个

 

三、分析

dump内存对象,并使用MAT分析, 查看哪些对象在使用FrameChannel

 
可以看见,一共4个FrameChannel对象,经过查看引用,发现3、4对象被Namespace中Holder引用,说明3、4是正常的连接;1、2没有被Hoder引用,是已经关闭的连接。选择第1个对象,查看谁引用它

 
共有3个对象引用它,
  1. 第一个this$0是FrameChannel的内部类DownstreamObserver,此内部类对象被grpc使用,经过代码分析,入口是FrameChannelStub,而此类只被Framechannel本身使用。
  2. 第二个arg$1是一个Lambda表达式生成的对象,此对象又被3个对象引用

 
查看这3个对象,再结果FrameChannel中设置指标监控的代码,可以知道是监控channelRoom所使用的Lambda表达式

进入guage方法

gauges即是上面第2个引用Lambda表达式的对象
 
再查看registry.register方法

metrics即是上面第1个引用Lambda表达式的对象
 
进入OnMetricAdded, 往下点几层,可看见

可见将gauge包装成JmxGuage,通过JMX暴露出来.
 
归纳一下,这三个引用对象所在的类分别是
  • 公司自己封装的Metrics指标类
  • com.codahale.metrics.MetricRegistry
  • com.codahale.metrics.jmx.JmxReporter
 
看一下,这三个类实例是什么时候被创建的
  • Metrics 是在最开始就会被创建

  • com.codahale.metrics.MetricRegistry和 com.codahale.metrics.jmx.JmxReporter 在 MetricsFactory 类被加载的时候就会被创建
 
MetricsFactory是一个监控指标的工具类,可以说是全局的,不会被JVM卸载,导致其引用的对象不会被释放。
 
  1. 引用FrameChannel的第三个对象是Session中的channels

 
channels是一个Map类型,其作用是存储namespace对应的frameChannel,在session第一次向后端业务系统发起事件时,会从Namespace中的Holder选择一个FrameChannel,放入自身channel的Map中缓存起来,下一次使用时直接从channels map中查询,不用从namespace holder中获取。
 
一个 session 对象代表一个客户端到长连接网关的连接,其是在客户端连接长连接网关时被创建的。
而session被3个对象引用,下面标的是4个,因为SessionRoom同时会被Namespace中的rooms和FrameChannel中的channelRooms引用

 

我们先看下SessionRoom,它会不会不被释放?

不会,因为NamespaceManager会定时(每30s)检查Namespace和FrameChannel中的SessionRoom是否为空,如果为空,则将其从rooms和channelRooms Map中删除,JVM就可以回收SessionRoom。
 

再看下SessionPool, 它会不会不释放Session?

不会,因为SessionPool也会定时检查已经关闭的Session,并将其删除
 

再看下ClientHead, 它会不会不被释放?

不会,ClientHead是Netty-SocketIO框架创建的对象,当客户端连接长连接网关时,会创建ClientHead对象,放入到ClientBox中,当连接关闭时,会将其中ClientBox中删除,具体请见类:com.corundumstudio.socketio.handler.ClientsBox
 
经过以上分析,发现使用 MetricsFactory 创建出的Metrics,在使用gauga等包含Lambda表达式的方法时,会使被引用的对象无法被GC回收,从而造成内存泄露。
 

四、总结

使用全局的对象时,最好不要直接引用生命周期变化的对象,如果非要引用其它对象,则保证被引用的对象也是全局的,不会被销毁重建,如果被引用对象会被销毁重建,则在销毁时,从全局对象中删除对其的引用,以免造成内存泄露。

JVM堆内存泄露分析的更多相关文章

  1. JVM内存结构、参数调优和内存泄露分析

    1. JVM内存区域和参数配置 1.1 JVM内存结构 Java堆(Heap) Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都 ...

  2. 【转】JVM 堆内存设置原理

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  3. 【Android】Eclipse Memory Analyzer 进行堆内存溢出分析

    MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件.     不同厂家的 JVM 所生成的堆转储文件在数据存储格式以及数据存储内容上有很多区别,但是比较主流的厂家和格式,例如 Sun, HP, ...

  4. JVM堆内存监测的一种方式,性能调优依旧任重道远

    上月,由极客邦.InfoQ和听云联合主办2016 APMCon中国应用性能管理大会圆满落下帷幕.会上,Java冠军Martijn Verburg进行了一场Java and the Machine的分享 ...

  5. [转]JVM 堆内存设置原理

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  6. JVM 堆内存设置原理

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  7. JVM 堆内存设置原理(转)

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  8. 转: 关于Linux与JVM的内存关系分析

    转自: http://tech.meituan.com/linux-jvm-memory.html Linux与JVM的内存关系分析 葛吒2014-08-29 10:00 引言 在一些物理内存为8g的 ...

  9. 《深入理解Java虚拟机》(六)堆内存使用分析,垃圾收集器 GC 日志解读

    堆内存使用分析,GC 日志解读 重要的东东 在Java中,对象实例都是在堆上创建.一些类信息,常量,静态变量等存储在方法区.堆和方法区都是线程共享的. GC机制是由JVM提供,用来清理需要清除的对象, ...

随机推荐

  1. Sparksql 日期加减函数

    1.日期加减spark sql 日期加减,date_sub,date_addval dateDF=spark.range(10) .withColumn("today",curre ...

  2. Linux CentOS7 安装配置 IPtables

    2021-08-11 1. 前言 防火墙其实就是实现 Linux 下访问控制功能的,分为硬件和软件的防火墙两种类型.无论在何网络中,防火墙工作的地方一定是网络的边缘.防火墙的策略.规则就是去定义防火墙 ...

  3. inotify与rsync实现实时同步记录文档

    目录 安装 配置 参考链接 安装 安装rsync yum -y install rsync 安装inotify-tools 这是一个实时监听文件变换的工具 wget -O /etc/yum.repos ...

  4. vue 手写倒计时,样式需要自己调。( 亲测可用,就是没有样式 )

    先写一个 js 文件,这个文件是工具类文件,需要单独开一个js // 计算出时间戳的具体数据:比如将85400转化为 n天n时n分n秒 export function formateTimeStamp ...

  5. 性能测试工具JMeter 基础(六)—— 测试元件: 线程组

    线程组的定义: 线程组是测试计划执行的入口,所有的逻辑控制器和取样器都必须在线程组下,其他的元件根据位置的不同作用域是不同的. 线程组是每个线程都是独立运行测试脚本,一个线程组就等于一个用户,通过多个 ...

  6. Linux从头学11:理解了这三个概念,才能彻底理解任务管理和任务切换

    作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...

  7. 借助AWR报告分析解决oracleCPU过高的问题

    原文地址:http://www.cnblogs.com/crystal-guoguo/p/4213458.html 简介:在oracle数据库中,有两个非常实用的自带监控工具EM(Enterprise ...

  8. select后给字段起别名,where和group后不能用,但having后可以

    为什么mysql having的条件表达式可以直接使用select后的别名? SQL语句的语法顺序: FROM -> WHERE -> GROUP BY -> HAVING -> ...

  9. Android学习记录(三)——安装SQLite

    这次学习安装SQLite. 一.SQLite简介 重要特性:零配置,即不需要复杂的配置即可使用 详细:https://www.runoob.com/sqlite/sqlite-intro.html 二 ...

  10. 关于Container容器以及IoC注入机制的认识

    container 容器的概念: 1 container 是一个Java 所编写的程序,用于对象之间之间管理对象关系. 主要的java EE 容器如下: Java容器类包含List.ArrayList ...