通常の後が、サーバが応答しない問題Tomcatのを忘れないでください

起源

最近では、顧客対応システムは、ページに表示される問題を調査した日は、将来の参照のためにここに記録を置かれた後、応答なし、開きません。

予備調査プロセス

Webページには、最初の時間は、サーバーが再起動されませんか、Tomcatのプロセスが閉じられたケースだったように、そのサービスの操作の最初の調査でも、次のコマンドを使用して、サーバー後というオープン疑われていません。

ps -ef|grep java

ここに画像を挿入説明
Tomcatは、その通常のサービスを発見した
サービスが正常であることから、それはCPUではないだろうか、メモリが使い果たさリードそれはありますか?以下のコマンドを使用して表示メモリ消費量

free -g

ここに画像を挿入説明
見つかったメモリ通常
、次のコマンドでビューCPU占有

top -bn 1 -i -c

ここに画像を挿入説明
67.5パーセントのCPU使用率ことがわかりました。ビット高いものの、それが爆発しませんでした。
そして、これはCPUが正常であった、プロセス、メモリ、非常に奇妙ですが、Tomcatは応答していません。
このようTomcatのスレッドプールとして今回、それはほぼ確実にできないサーバーの問題が、問題のJavaプログラム自体は、など、フルである、またはメモリリーク

最終調査プロセス

ヒープメモリ使用量の最初の分析

我々はすでにPID 12945として、次のコマンドを使用してJavaプロセスをとっています

ps -ef|grep java

ここに画像を挿入説明
jmapのことで次のヒープメモリ分析ツールケース

jmap -heap 12945

ここに画像を挿入説明
これは、古い年の占領の問題は、実際に99.99%に達していることがわかりました。99.99%に、古い年は世代コレクションアルゴリズムの基本を理解する必要があります理由を理解します。

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
新生代的回收算法:
每当新创建一个对象时优先在Eden区分配,而此时Eden区内存不足的时候,就会进行Minor GC,然后Eden区仍然活着的对象会复制到to Survivor区,而from Survivor区中年龄超过阀值的对象会保存到老年代中,没有超过阀值的对象会保存到to Survivor区中,然后to Survivor区和from Survivor区互换位置,保证每次都有一块Survivor区是空闲的。
老年代的回收算法:
在进行Minor GC之前,JVM会先检查老年代最大可用连续空间是否大于新生代历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,否则就进行Full GC(全局GC)。说人话就是老年代会根据以前新生代给的数据量的一个平均值来评估剩下的空间能不能放下下一次新生代给的数据量,如果不能那就要先回收一下自己。

为什么老年代没有被回收?
老年代是99.99%,很显然是老年代的内存没有回收掉,那么为什么没有回收掉呢?我们先看看GC线程的情况

jstat -gcutil 12945 1000 10

1000为统计的间隔,单位为毫秒,10为统计的次数,输出如下:
ここに画像を挿入説明
从输出中同样可以看到E(Eden)区与O(Old)区都已经被占满了。其他几个输出项的含义如下:

  • YGC: 从启动到采样时Young Generation GC的次数
  • YGCT: 从启动到采样时Young Generation GC所用的时间 (s).
  • FGC: 从启动到采样时Old Generation GC的次数.
  • FGCT: 从启动到采样时Old Generation GC所用的时间 (s).
  • GCT: 从启动到采样时GC所用的总时间 (s).

可以看到JVM一直在尝试回收老年代,但是一直没能将内存回收回来。

接下来看看java进程的pid的cpu情况

top -p 12945 -H

ここに画像を挿入説明
-p用于指定进程,-H用于获取每个线程的信息,从top输出的内容,可以看到有很多线程占用了非常高的CPU:
高占用cpu的线程在干嘛?
jstack是java虚拟机自带的一种堆栈跟踪工具,用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。使用下面命令,将java进程的堆栈信息打印到文件中:

jstack -l 12945 > stack.log

在线程堆栈信息中,线程id是使用十六进制来表示的。将上面几个线程的id转化为16进制然后去stack.log中搜索
ここに画像を挿入説明
可以看到这几个线程是GC线程,那么就可以确定了,回收器一直在尝试回收内存,但是因为某种原因又一直回收不掉,回收器线程就把cpu占满了。而老年代回收不掉,那么系统就没法运行,也就是表现出来cpu会有一点并且tomcat没有响应的情况。

为什么垃圾收集器一直在运行但老年代没法被回收?
要知道为什么没法被回收我们要先知道具体是哪些顽固对象导致垃圾处理器回收不掉。我们需要先把当前的堆情况导出来,然后使用工具进行分析。

jmap -dump:live,format=b,file=headInfo.hprof 12945

live这个参数表示我们需要抓取的是目前在生命周期内的内存对象,也就是说GC收不走的对象,在这种场景下,我们需要的就是这些内存的信息。

小插曲:有些时候你使用这个命令会报错:

Unable to open socket file: target process not responding or HotSpot VM not loaded

那么这是什么原因呢?其实解决这个问题最好的办法就是程序重启。但想必你也想到了,如果程序重启的话那么当前的堆栈信息也都没有了。那么有什么好的解决办法呢?可以看看这篇文章
其大意是说,java的pid文件可能已经被系统给删除了。linux系统有个删除管理机制:系统每天会用tmpwatch命令检查并删除 /tmp 下超过240小时未访问过的文件和目录。
我们需要去修改这个机制,否则pid文件就可能被删除。
但是:但是:但是:我在/etc/cron.daily下面并没有找到什么tmpwatch文件。具体原因好像是说我没安装tmpwatch这个服务。那么问题来了,既然我没安装,为什么也会删除我的PID呢。最后的解决办法是重启了服务,虽然重启服务后堆栈信息被清除了,但好在这个问题很快又复现了,所以我顺利的通过上面的命令拉取到了当前的堆栈信息。

生成了hprof文件后,可以拉回到本地进行分析了。生成的hprof文件会很大,我们可以先采用如下命令压缩再拉回本地

gzip -7 headInfo.hprof

-7代表压缩比例,数值越大压缩出来的文件越小但压缩所需时间会越高,最大值为9。

拉回本地后,就可以使用VisualVM或者MemoryAnalyzer来对文件进行分析,这两个内存分析工具的下载链接我会放到文末。
VisualVM分析得如下
ここに画像を挿入説明
可以看到主要是这些YXXXX,YXXXX的字符串占用了大量的内存,那么这些YXXXX,YXXXX是什么就是具体和你自己的业务有关的了。
个人觉得MemoryAnalyzer更好用一点,下面重点介绍MemoryAnalyzer的分析结果
MemoryAnalyzer分析一下得如下结果
ここに画像を挿入説明
点这里可以看到哪些东西占用了最大的内存
ここに画像を挿入説明
也可以看到是这些YXXXX,YXXXX的组合串占用了比较大的内存,那么这些YXXXX,YXXXX的组合串是哪里产生的呢?可以通过点击如下地方跟踪到具体的代码,这些代码也是可能造成内存泄漏的地方
ここに画像を挿入説明
ここに画像を挿入説明
点击这里跟踪堆栈代码
ここに画像を挿入説明
这里很清晰的列出了,IncompatibilityServiceImpl这个类的combination这个方法造成了这些字符串的分配问题。我们来看这里的代码是有什么问题。

public List<String> combination(List<RecipeItem> recipeItems) {
    List<String> list = new ArrayList<>();
    int n = recipeItems.size();
    int nbit = 1 << n;
    for (int i = 0; i < nbit; i++) {
        StringBuilder x =new StringBuilder("");
        for (int j = 0; j < n; j++) {
            int tmp = 1 << j;
            if ((tmp & i) != 0 && i != tmp) {
                x.append(",").append(recipeItems.get(j).getDrugCode());
            }
        }
        if (!"".equals(x.toString())) {
        	list.add(x.toString().substring(1));
        }
    }
    return list;
}

这里是一个排列组合算法,是需要把一堆的字符串排列组合,比如用户输入ABC。我需要组合成AB,AC,BC,ABC这种。

总结

问题到这里基本上全部明确了
这个算法在需要组合的字符串量不大的时候组合的结果倒是不多,但是一旦字符串多的时候就很可怕了,组合结果将随着字符串的增多呈指数倍增长。
我测试过,当需要组合20个字符串时,组合情况竟达到了104W之多。这么多字符组合串因为后续还需要一个个去数据库查询,会导致这个线程需要执行很久很久才能执行完毕,但是在线程执行的时候这些字符组合串对象都是不能被回收的,那么就可能导致内存被耗尽,也就是前面说显现的老年代被占满的而又没法回收的情况。

記事ツールのダウンロードリンク

visualvm143
MemoryAnalyzer

します。https://www.cnblogs.com/QG-whz/p/9647614.htmlから参考と一部抜粋

114元記事公開 ウォンの賞賛146 ビューに35万+を

おすすめ

転載: blog.csdn.net/qq32933432/article/details/103475948