• 转 从一个OutOfMemoryError 学会了分析Java内存泄漏问题


     
     
     
    以前都是好好的,最近出现了 oom。

    问题


    开始是: java.lang.OutOfMemoryError: Java heap space
     View Code
    512M 不够吗? 很有可能啊...
    增加内存到1G 后仍然出现问题:Failed to mark a promise as failure because it has failed already: [DefaultChannelPromise@33a99639(failure: io.netty.handler.codec.EncoderException: java.lang.OutOfMemoryError: GC overhead limit exceeded), io.netty.handler.codec.EncoderException: java.lang.OutOfMemoryError: GC overhead limit exceeded
     View Code

    这就奇怪了! 注意到 出现次数比较多是 com.lkk.platform.system.domain.service.ELCommonCodeRegulationService, method: GetCode,

    复制代码
        @Transactional(readOnly = false)
        public String GetCode(String name){
            RLock rlock = redissonManager.getRedisson().getLock(name);
            boolean getLock = false;
            try{
                getLock = rlock.tryLock(3, 20, TimeUnit.SECONDS);
                if (getLock){
                    ELCodeDef elCodeDef = findCommonCode(name);
                    super.updateById(elCodeDef);
                    return elCodeDef.getCode();
                }
            }catch (Exception ex){
                ex.printStackTrace();
            }finally {
                if (getLock) {
                    rlock.unlock();
                }
            }
            return "";
        }

    而
        @Autowired
        RedissonManager redissonManager;
     
    复制代码

    分析

    由此怀疑这个地方有些问题。 虽然出现了oom, 但是进程没有死, 似乎依然可以响应某些请求,于是把线程dump 下来, 观察一番,发现 redisson-netty 竟然有上千个

    就是这个

    复制代码
    "redisson-netty-25-32" #808 prio=5 os_prio=0 tid=0x00007f7ec0187800 nid=0x3625 runnable [0x00007f7e77d6c000]
       java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
        at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
        at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
        at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
        - locked <0x00000000e90b27a0> (a io.netty.channel.nio.SelectedSelectionKeySet)
        - locked <0x00000000e90b27f8> (a java.util.Collections$UnmodifiableSet)
        - locked <0x00000000e90b2708> (a sun.nio.ch.EPollSelectorImpl)
        at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
        at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
        at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:786)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:434)
        at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.lang.Thread.run(Thread.java:748)
    复制代码

    太不正常了!  但是 这里的redisson-netty- 仍然是 RUNNABLE 状态, 看起来也不是问题啊!  仔细检查了下, 也没发现死锁啊!!

    那就不是线程问题吗?

    redisson 的bug 吗? redisson 的官网 的issue 搜索一番,无果。 郁闷了! 而且我的 redisson 版本是 3.1.1, 已经很新的了吧!!

    堆栈分析吧!!把java 的heap 拔下来,

    jps -l,  然后 jmap -dump:format=b,file=dumpFileName pid 

    看到有些异常:

    肯定不是 spring 的classloader 吧。

     

    看到 netty 的PoolThreadCache 比较可疑啊, 还有 mybatis。

     

    Biggest Top-Level Dominator Packages 跟之前一样的提示, 一个是netty的 PollThreadCache, 一个是netty 的epoll, 还有是redision, 还有是sun 的EPollArrayWrapper, 还有mybatis,其他 也看不出什么来啊!

    分析只能到此为止了吗? io.netty.buffer.PoolThreadCache 是什么东东? 我不熟悉啊!  看过netty 源码, 已经全忘了!

    是内存泄漏吗?  好像也看不出来。 不太确定。 网上搜索看看吧!!

    还是从redision 入手吧。  咦, redision 的用法好像不太对哦!!! 改一下吧:

    复制代码
        @Autowired
        RedissonClient redissonClient;
    ==>
        @Autowired
        RedissonManager redissonManager;
    
    
        RLock rlock = redissonClient.getLock(name); ==>         RLock lock = redissonManager.getRedisson().getLock(name);
    复制代码

    而RedissonManager如下:

    复制代码
    import org.apache.commons.lang3.StringUtils;
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.redisson.config.ReadMode;
    import org.redisson.config.SentinelServersConfig;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    @Component
    public class RedissonManager {
    
        @Autowired
        RedisTemplate<String, Object> redisTemplate;
    
        @Value("${spring.redis.password}")
        private String redisPassword;
    
        @Value("${spring.redis.port}")
        private String redisPort;
    
        @Value("${spring.redis.host}")
        private String redisHost;
    
        @Value("${spring.redis.timeout}")
        private String redisTimeout;
    
        @Value("${spring.redis.sentinel.node}")
        private String redisSentinelNode;
    
        @Value("${spring.redis.sentinel.master}")
        private String redisSentinelMaster;
    
        @Bean
        public RedissonClient getRedisson() {
            Config config = new Config();
            if (StringUtils.isNotEmpty(redisPort)) {
                config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort).setPassword(redisPassword);
            } else if (StringUtils.isNotEmpty(redisSentinelNode)) {
                String[] nodes = redisSentinelNode.split(",");
                List<String> newNodes = new ArrayList(nodes.length);
                Arrays.stream(nodes).forEach((index) -> newNodes.add(index.startsWith("redis://") ? index : "redis://" + index));
                SentinelServersConfig serverConfig = config.useSentinelServers()
                        .addSentinelAddress(newNodes.toArray(new String[0]))
                        .setMasterName(redisSentinelMaster)
                        .setReadMode(ReadMode.SLAVE)
                        .setTimeout(Integer.valueOf(redisTimeout));
                if(StringUtils.isNotEmpty(redisPassword)){
                    serverConfig.setPassword(redisPassword);
                }
            }
            return Redisson.create(config);
        }
    }
    复制代码

    改了就好了!!突然自己明白了, 原来就是这个redision 用法错误导致的!!

    不信? 重新拔下来heap dump 分析一下:

    最大的 com.mysql.cj.jdbc.AbandonedConnectionCleanupThread 才占用2m, 不是什么问题。 可见已经没有了什么

    PoolThreadCache 已经下滑到了第七位, 总占用7M ,38个对象,看起来正常了许多!! :

    总结

    花了2天时间终于搞定!!

    其实上面的 thread dump 和 heap dump 已经给出了比较明显的答案了!! 就是 PoolThreadCache 占用了 过多的内存, 其原因就是 PoolThreadCache 错误的创建了 太多!————  本来应该是单例的 对象, 被搞成了 prototype, 你说是不是引起了大错!!! 一个 PoolThreadCache占用内存差不多196,000byte, 921个就 是 180516000 byte 也就是 差不多 下图的180M, 一类对象就 180M, 总共才1G, 当然会不够用!!

    其实 从错误日志也可以 分析出来一些, 在创建需要比较大的内存的对象的时候, 就会出现 oom, 因为内存确实已经不够了啊!! (这也是为什么 ELCommonCodeRegulationService 的 GetCode 方法调用的时候,出现了很多oom。 但是又不是绝对的。 因为其他 地方也可以创建大内存对象)

    其实只要再多问几个问题就知道了答案:  这个对象为什么出现了这么多次, 占用这么多内存呢??  这个是正常的吗? 如果能够很早认识到这些问题,并回答之, 那么问题就不是大问题了,就不会浪费很多时间了!

  • 相关阅读:
    进度条
    打开文件的功能代码 JFileChooser
    我对JAVA的初认知
    集合之五:Set接口
    集合之四:List接口
    集合之三:泛型
    Maven web项目(简单的表单提交) 搭建(eclipse)
    集合之二:迭代器
    集合之一:集合概述
    java的函数
  • 原文地址:https://www.cnblogs.com/python-xiakaibi/p/13411680.html
Copyright © 2020-2023  润新知