[转帖]Cat导致内存不足原因分析

cat,导致,内存不足,原因,分析 · 浏览次数 : 0

小编点评

```java public void reset() { DefaultMessageManager.Context ctx = (DefaultMessageManager.Context)this.m_context.get(); if (ctx != null) { if (ctx.m_totalDurationInMicros == 0L) { ctx.m_stack.clear(); ctx.m_knownExceptions.clear(); this.m_context.remove(); } else { // 这里会释放错误日志堆栈信息 ctx.m_knownExceptions.clear(); for (Exception e : ctx.m_knownExceptions) { e.printStackTrace(); } this.m_context.remove(); } } } 。归纳总结以上内容,生成内容时需要带简单的排版 } ``` **方法说明:** 1. **`DefaultMessageManager.Context`**获取上下文,如果上下文为 null,则会清除异常堆栈信息。 2. **``m_context.remove()`**移除上下文。 3. **``m_context.remove()`**移除上下文中的所有事件和异常。 4. **``m_context.remove()`**移除上下文中的所有已知异常。 5. **``ctx.m_stack.clear()`**清除错误日志堆栈信息。 6. **``ctx.m_knownExceptions.clear()`**清除已知异常。 7. **``ctx.m_knownExceptions.clear()`**释放错误日志堆栈信息。 8. **``m_context.remove()`**移除上下文。 9. **``m_context.remove()`**移除上下文中的所有事件和异常。

正文

背景

线上几亿的数据在回刷的时候容器服务会出现OOM而重启,导致任务中断

内存泄露分析

jmap -histo pid 找出了有几十亿的java.lang.StackTraceElement对象,找不到被谁引用了
jmap -dump:format=b,file=heapdump.hprof pid dump内存
下载到本机mac上,用mat(MemoryAnalyzer)分析,得到内存泄露报告,看到内存全部被com.dianping.cat.message.internal.DefaultMessageManager$Context引用,找到了罪魁祸首
在这里插入图片描述

原因分析

com.dianping.cat.log4j.Log4j2Appender 在打印错误Exception的时候会调用Cat.logError方法

public class Log4j2Appender extends AbstractAppender {
  ....
    public void append(LogEvent event) {
        try {
            Level level = event.getLevel();
            if (level.isMoreSpecificThan(Level.WARN)) {
                this.logError(event);
            }
        } catch (Exception var3) {
            if (!this.ignoreExceptions()) {
                throw new AppenderLoggingException(var3);
            }
        }
    }
   ....
    private void logError(LogEvent event) {
        Throwable exception = event.getThrown();
        if (exception != null) {
            Message message = event.getMessage();
            if (message != null) {
                Cat.logError(message.getFormattedMessage(), exception);
            } else {
                Cat.logError(exception);
            }
        }
<span class="token punctuation">}</span>

}

    Cat类的logError函数最终调用到了DefaultMessageManager.shouldLog方法

    public class Cat {
        ...
        public static void logError(String message, Throwable cause) {
            try {
                getProducer().logError(message, cause);
            } catch (Exception var3) {
                errorHandler(var3);
            }
    
    <span class="token punctuation">}</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">logError</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> cause<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span>
            <span class="token function">getProducer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">logError</span><span class="token punctuation">(</span>cause<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> var2<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
            <span class="token function">errorHandler</span><span class="token punctuation">(</span>var2<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    

    }

      public class DefaultMessageProducer implements MessageProducer {
          public void logError(String message, Throwable cause) {
              if (Cat.getManager().isCatEnabled()) {
                  if (this.shouldLog(cause)) {
                     ....
                  }
              } else {
                  cause.printStackTrace();
              }
          }
              private boolean shouldLog(Throwable e) {
              return this.m_manager instanceof DefaultMessageManager ? ((DefaultMessageManager)this.m_manager).shouldLog(e) : true;
          }
      

        DefaultMessageManager类的m_context在shouldLog的时候把异常堆栈保存下来了,如果Cat事务不关闭,随着异常越来越多就导致了内存溢出

        public class DefaultMessageManager {
            private ThreadLocal<DefaultMessageManager.Context> m_context = new ThreadLocal();
            boolean shouldLog(Throwable e) {
                DefaultMessageManager.Context ctx = (DefaultMessageManager.Context)this.m_context.get();
                return ctx != null ? ctx.shouldLog(e) : true;
            }
        
        <span class="token keyword">class</span> <span class="token class-name">Context</span> <span class="token punctuation">{<!-- --></span>
            <span class="token comment">// 内存不足就是由于错误堆栈信息没有限制导致的</span>
            <span class="token keyword">private</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Throwable</span><span class="token punctuation">&gt;</span></span> m_knownExceptions<span class="token punctuation">;</span>
        
            <span class="token keyword">public</span> <span class="token class-name">Context</span><span class="token punctuation">(</span><span class="token class-name">String</span> domain<span class="token punctuation">,</span> <span class="token class-name">String</span> hostName<span class="token punctuation">,</span> <span class="token class-name">String</span> ipAddress<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
                <span class="token keyword">this</span><span class="token punctuation">.</span>m_knownExceptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        
            <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">shouldLog</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>m_knownExceptions <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token keyword">this</span><span class="token punctuation">.</span>m_knownExceptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
        
                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>m_knownExceptions<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token comment">// 这里没有限制大小,只要有异常就往Set里面添加,这里应该做一个优化</span>
                    <span class="token keyword">this</span><span class="token punctuation">.</span>m_knownExceptions<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        

        }
        }

          解决方案

          手动调用Cat.getManager().reset();方法清空保存的异常堆栈信息

              public void reset() {
                  DefaultMessageManager.Context ctx = (DefaultMessageManager.Context)this.m_context.get();
                  if (ctx != null) {
                      if (ctx.m_totalDurationInMicros == 0L) {
                          ctx.m_stack.clear();
                          ctx.m_knownExceptions.clear();
                          this.m_context.remove();
                      } else {
                          // 这里会释放错误日志堆栈信息
                          ctx.m_knownExceptions.clear();
                      }
                  }
              }
          

            与[转帖]Cat导致内存不足原因分析相似的内容:

            [转帖]Cat导致内存不足原因分析

            背景 线上几亿的数据在回刷的时候容器服务会出现OOM而重启,导致任务中断 内存泄露分析 jmap -histo pid 找出了有几十亿的java.lang.StackTraceElement对象,找不到被谁引用了 jmap -dump:format=b,file=heapdump.hprof pid

            [转帖]Linux中split大文件分割和cat合并文件详解

            https://www.yingsoo.com/news/servers/70195.html 当需要将较大的数据上传到服务器,或从服务器下载较大的日志文件时,往往会因为网络或其它原因而导致传输中断而不得不重新传输。这种情况下,可以先将大文件分割成小文件后分批传输,传完后再合并文件。 1. 分割文件

            [转帖]采用cat与EOF组合添加多行内容时防止变量解析的解决办法

            https://blog.51cto.com/xoyabc/1718355 【问题描述】 当采用cat与EOF组合添加多行内容时,若含有变量,则追加后的文件中是变量对应的的值,并不是变量本身。 如$a对应的值为111,执行以下命令后 cat >> /etc/profile << EOF $a $a

            [转帖]Linux终端:用cat命令查看不可见字符

            https://developer.aliyun.com/article/80607 printf 'testing\012\011\011testing\014\010\012more testing\012\011\000\013\000even more testing\012\011\011

            [转帖]Linux清空正在运行的log日志文件内容

            cat /dev/null > file.log

            [转帖]prometheus部暑redis_exporter(sentinel, cluster实战)

            redis_exporter #创建工作目录 mkdir /date/redis -p cat > /data/redis/start.sh << 'EOF' docker run -d \ --name redis \ -p 6379:6379 \ redis redis-server \ --a

            [转帖]Linux shell 按行循环读入文件常用代码如下:

            Linux shell 按行循环读入文件常用代码如下: #/bin/bash printf "*************************************\n" echo " cat file whiel read line" cat test.txt |while read line

            [转帖]prometheus监控nginxt的两种方法(vts)

            方法一 使用nginx_ vts_exporter mkdir -p /data/nginx/{log,conf/conf.d} cat > /data/nginx/conf/nginx.conf << 'EOF' user root; worker_processes auto; error_lo

            [转帖]kubernetes ceph

            kubernetes ceph https://www.jianshu.com/p/e628da68328d 安装软件 在所有节点上添加清华大学的ceph镜像源 #cat >> /etc/yum.repos.d/ceph.repo << EOF [ceph] name=Ceph baseurl=ht

            [转帖]shell脚本循环查询数据库实现数据输出到csv

            https://blog.csdn.net/m0_46897923/article/details/125906115?spm=1001.2014.3001.5501 可以利用这个思路高点事情吧.. 一、shell脚本和数据文件 1.shell脚本 cat data.txt|while read l