zoukankan      html  css  js  c++  java
  • 转 从一个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。 但是又不是绝对的。 因为其他 地方也可以创建大内存对象)

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

  • 相关阅读:
    hdu 5723 Abandoned country 最小生成树 期望
    OpenJ_POJ C16G Challenge Your Template 迪杰斯特拉
    OpenJ_POJ C16D Extracurricular Sports 打表找规律
    OpenJ_POJ C16B Robot Game 打表找规律
    CCCC 成都信息工程大学游记
    UVALive 6893 The Big Painting hash
    UVALive 6889 City Park 并查集
    UVALive 6888 Ricochet Robots bfs
    UVALive 6886 Golf Bot FFT
    UVALive 6885 Flowery Trails 最短路
  • 原文地址:https://www.cnblogs.com/python-xiakaibi/p/13411680.html
Copyright © 2011-2022 走看看