Redis订阅模式在生产环境引起的内存泄漏

本文讨论了内存泄漏的概念,以Android5.0的内存问题为例,然后聚焦于作者在实际开发中遇到的WebSocket项目中的内存泄漏,主要源于对Redis消息监听容器的不当使用。通过分析代码,揭示了Spring和WebSocketSession如何导致内存泄漏问题,强调了对垃圾回收机制和框架原理的理解在开发中的重要性。
摘要由CSDN通过智能技术生成

内存泄漏

  内存泄漏指的就是在运行过程中定义的各种各样的变量无法被垃圾回收器正常标记为不可达并触发后续的回收流程,主要原因还是因为对可回收对象引用没有去除,导致垃圾回收器通过GC ROOT可达性分析时认为当前是可达的;这时随着系统的运行时间,累积的不可回收的对象就越多,直到垃圾回收器执行Full GC还是没有空余空间存放新加入的对象,这时虚拟机就会抛出out of memory错误。此种错误可以分类为内存泄漏导致的,原因是应该回收的对象无法被垃圾回收器正常回收从而导致内存不足。说起内存泄漏近十年引起比较大的是便是Android 5.0引起的内存问题,该Bug导致手机在使用一段时间后必须手动重启系统释放内存;不然,无法运行任何应用,包括系统自带APPS桌面等都会引起FC崩溃;这时查看内存占用发现可用内存为负数。安卓5.0导致的内存泄漏:Android 5.0臭名昭著的内存泄露Bug终于修复 - Google - LUPA开源社区

接下来说一个我在实际开发中遇到的内存泄漏问题,该问题在测试环境不易发现,主要原因有以下几点:

1、测试环境经常更新重启,相当于GC回收了对象

2、用户量太少了,根本撑不到内存泄露出现的那一刻

但是,生产环境那就不一样了,用户多,运行时间久,如果存在长期没有被回收的对象时,久而久之就会触发内存泄漏的情况

言归正传,生产环境出现的内存泄露问题是由对Redis订阅使用不当导致,下面我把引起内存泄漏的代码贴上来

1   /**
 2      * 因为@ServerEndpoint不支持注入,所以使用SpringUtils获取IOC实例
 3      */
 4     private StringRedisTemplate redisTampate = SpringUtils.getBean(StringRedisTemplate.class);
 5     private RedisMessageListenerContainer redisMessageListenerContainer = SpringUtils.getBean(RedisMessageListenerContainer.class);
 6     private Session session;
 7 
 8     @OnOpen
 9     public void onOpen(Session session, @PathParam("topic") String topic, @PathParam("username") String username) {
11         this.session = session;
12         sessions.add(this);
13         SubscribeListener subscribeListener = new SubscribeListener();
14         subscribeListener.setSession(session);
15         subscribeListener.setStringRedisTemplate(redisTampate);
17         try {
18             redisMessageListenerContainer.addMessageListener(subscribeListener, new ChannelTopic(topic));
19         } catch (Exception e) {
20             e.printStackTrace();
21         }
22     }

这是一个WebSocket的项目,利用即时通信的特性实现了由后台触发前端页面的刷新效果。

查看上述的代码眼尖的人应该已经发现问题所在了吧?这就是一个使用Spring和Redis不当导致的内存泄露问题

接下来我们分析

  首先代码的4到7行申明了三个成员变量,当然,主要关注点还是第5行的RedisMessageListenerContainer 变量,这是从SpringContext中取出了Redis的消息监听容器;接下来在onOpen方法里定义了获取了WebSocket连接成功后产生而Session会话,这里的Session会话不是WebSession而是WebSocketSession,可以定义为会话帧,每次用户连接成功之后服务器就分发一个WebSocketSession给当前用户,当断开连接时该会话帧就会断开对象引用,垃圾回收器就可以回收;说到这里其实问题已经非常明显了,那就这个WebSocketSession压根就没有被垃圾回收器回收掉,每次用户连接就产生一个WebSocketSession对象,并通过地14行代买引用给Redis的监听器,然后再由将监听器添加到Redis消息监听容器中,而Redis消息监听容器又是从SpringContext中取出的,那就意味着该对象是一个SpringIOC中的Bean实例,这是一个有GC ROOT引用的对象;这样一来后续产生的每一个WebSocketSession会话帧都会被Redis消息监听容器的实例引用,垃圾回收器在进行可达性分析时都认为该对象是可达的,判定无法回收,从而就导致了内存泄漏。

  这个问题其实是对垃圾回收器和Spring原理不了解导致的,在日常开发中应该尽可能的避免这些问题

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值