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


    内存泄漏

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

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

    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中取出由Spring托管;那么就意味着该对象是一个SpringContext中的Bean实例,这一个由GC ROOT引用的对象;这样一来后续产生的每一个WebSocketSession会话帧都会被Redis消息监听容器的实例引用,垃圾回收器在进行可达性分析时都认为该对象是可达的,判定无法回收,从而就导致了内存泄漏。

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

  • 相关阅读:
    计算日期到天数转换
    cookie和session的跨域怎么解决?
    MyBatis:关联查询
    二十六、设置时序电路初始状态的方法
    【CSDN|每日一练】小艺的英文名
    Haproxy 透传IP配置方法及测试
    fonts什么文件夹可以删除吗?fonts文件夹删除了怎么恢复
    实验5:编写、调试具有多个段的程序
    【JVM技术专题】TLAB内存分配+锁的碰撞技术串烧「难点-核心-遗漏」
    C. Fibonacci Words-April Fools Day Contest 2021
  • 原文地址:https://www.cnblogs.com/Tegra/p/17995864