当前位置: 首页 > Web前端 > HTML5

Java在线排查,纯干货推荐收藏

时间:2023-04-04 23:04:31 HTML5

在线故障主要有CPU、磁盘、内存、网络问题,大部分故障可能包括不止一级的问题,所以排查的时候尽量从四个方面检查一下逐个。同时jstack、jmap等工具不局限于某一方面的问题。基本问题就是df,free,top3连续,然后jstack和jmap轮流服务,具体问题可以具体分析。CPU一般来说,我们会先排查CPU的问题。CPU异常往往比较容易定位。原因包括业务逻辑问题(死循环)、频繁的gc、过多的上下文切换。最常见的往往是业务逻辑(或者框架逻辑)引起的,可以使用jstack来分析对应的栈情况。使用jstack分析CPU问题,我们先用ps命令找到对应进程的pid(如果你有多个目标进程,可以用top查看哪个进程占用高)。在这里,小编建了一个前端学习交流按钮群:132667127,自己整理的最新前端资料和进阶开发教程。如果愿意,可以进群一起学习交流,然后用top-H-ppid找一些CPU占用率比较高的线程,把占用最高的pid转成16进制printf'%x\n'pid获取nid然后直接在jstack中找到对应的栈信息jstackpid|grep'nid'-C5-Color可以看到我们找到了nid为0x42的栈信息,接下来只要仔细分析就可以了。当然,我们更常见的是分析整个jstack文件。通常我们会比较关注WAITING和TIMED_WAITING这两个部分,更不用说BLOCKED了。我们可以使用命令catjstack.log|grep"java.lang.Thread.State"|排序-nr|uniq-c可以全面了解jstack的状态。如果WAITING之类的太多了,那可能是有问题了。频繁的gc当然我们还是会用jstack来分析问题,但是有时候我们可以先判断是不是gc太频繁了,使用命令jstat-gcpid1000观察gc代数的变化,1000表示采样间隔(ms),S0C/S1C,S0U/S1U,EC/EU,OC/OU,MC/MU分别代表两个Survivor区,Eden区,老年代,元数据区的容量和使用情况。YGC/YGT、FGC/FGCT、GCT代表YoungGc和FullGc的耗时、次数和总耗时。如果看到gc比较频繁,再对gc做进一步分析。具体可以参考gc章节的描述。上下文切换对于频繁出现的上下文问题,我们可以使用vmstat命令查看cs(contextswitch)列,它代表上下文切换的次数。如果我们要监控某个特定的pid,可以使用pidstat-wpid命令,cswch和nvcswch代表自愿和非自愿切换。?磁盘磁盘问题与CPU一样基本。首先是磁盘空间。我们直接使用df-hl查看文件系统状态。大多数时候,磁盘问题仍然是性能问题。我们可以使用iostatiostat-d-k-x分析最后一列%util可以看到每个磁盘的写入程度,rrqpm/s和wrqm/s分别代表读写速度,一般可以帮助定位具体是块盘有问题。另外,我们还需要知道哪个进程在读写。一般来说,开发者心知肚明,还是使用iotop命令来定位文件读写的来源。但是我们这里得到的是tid,我们需要把它转换成pid,我们可以通过readlink-f/proc/*/task/tid/../....找到pid。找到pid后,可以看具体的进程的读写状态。cat/proc/pid/io我们也可以通过lsof命令来判断具体的文件读写状态。还有更多。主要包括OOM、GC问题和堆外内存。一般来说,我们会先用free命令查看内存的各种情况。堆内存问题大部分也是堆内存问题。外观主要分为OOM和StackOverflo。InsufficientmemoryinOOMJMV,OOM大致可以分为以下几种:,基本上是线程池代码有问题,比如忘记shutdown,所以应该先从代码层面找问题,用jstack或者jmap。如果一切正常,JVM端可以通过指定Xss来减小单线程栈的大小。另外,在系统层面,可以修改/etc/security/limits.confnofile和nproc,增加os对线程的限制Exceptioninthread"main"java.lang.OutOfMemoryError:Javaheapspace这表示heap已经达到-Xmx设置的最大值,这应该是最常见的OOM错误。解决办法还是先在代码中查找,怀疑是内存泄漏,使用jstack和jmap定位问题。如果一切正常,需要通过调整Xmx的值来扩展内存。Causedby:java.lang.OutOfMemoryError:Metaspace表示元数据区的内存使用已经达到XX:MaxMetaspaceSize设置的最大值。排查思路与上述一致。可以通过XX:MaxPermSize调整参数(这里先不说1.8之前的永久代)。StackOverflow栈内存溢出,这个你见多了。Exceptioninthread"main"java.lang.StackOverflowError表示线程栈需要的内存大于Xss值,也是先检查。通过Xss调整参数,但如果调整过大,可能会造成OOM。使用JMAP定位代码内存泄漏。对于上述OOM和StackOverflo代码排查,我们一般使用JMAPjmap-dump:format=b,file=filenamepid导出dump文件,通过mat(EclipseMemoryAnalysisTools)导入dump文件进行分析。对于内存泄漏,我们一般可以直接选择LeakSuspects,mat给出了内存泄漏的建议。或者,选择TopConsumers查看最大的对象报告。与线程相关的问题可以通过选择线程概述进行分析。另外选择Histogram类的overview自己慢慢分析。可以在mat上搜索相关教程。在日常开发中,代码内存泄漏是比较常见和隐蔽的,需要开发者多注意细节。比如每次请求都会创建新的对象,导致大量重复创建对象;执行文件流操作但未正确关闭;gc被手动触发不当;ByteBuffer缓存分配不合理等,会导致代码OOM。另一方面,我们可以在启动参数中指定-XX:+HeapDumpOnOutOfMemoryError来保存发生OOM时的dump文件。gc问题和线程gc问题不仅会影响CPU还会影响内存,排查思路是一样的。一般用jstat查看分代变化,比如youngGC或fullGC次数是否过多;EU、OU等指标增长是否异常。线程太多,没有及时gc也会导致oom,多数是前面提到的无法创建新的nativethread。除了jstack对dump文件的详细分析外,我们一般先看线程整体,通过pstreee-ppid|wc-l。或者直接通过查看/proc/pid/task的数量就是线程数。如果堆外内存遇到了堆外内存溢出,那就真的很不幸了。首先,堆外内存溢出的表现是物理常驻内存快速增长。如果报错,则取决于使用方法。如果是使用Netty导致的,错误日志中可能会出现OutOfDirectMemoryError错误。如果直接是DirectByteBuffer,会报OutOfMemoryError:Directbuffermemory。堆外内存溢出往往与NIO的使用有关。一般我们先通过pmappmap-xpid|查看进程占用的内存排序-rn-k3|head-30,表示倒序查看pid对应的前30大内存段。这里可以过段时间再运行命令,看看内存增长情况,或者与正常机器相比,可疑的内存段在哪里。如果我们确定有可疑的内存终端,就需要通过gdb进行分析。gdb--batch--pid{pid}-ex"dumpmemoryfilename.dump{memorystartaddress}{memorystartaddress+memoryblocksize}"得到dump文件后,可以用hexdump查看hexdump-Cfilename|少,但是你看到的大部分文件都是二进制乱码。NMT是Java7U40引入的HotSpot的新特性。通过jcmd命令,我们可以看到具体的内存组成。需要在启动参数中加上-XX:NativeMemoryTracking=summary或??-XX:NativeMemoryTracking=detail,性能会有轻微损失。一般对于堆外内存增长缓慢直至爆发的情况,可以先设置一个baselinejcmdpidVM.native_memorybaseline。然后等待一段时间查看内存增长情况,通过jcmdpidVM.native_memorydetail.diff(summary.diff)做一个summary或??者detail级别的diff。可以看到jcmd分析的内存非常详细,包括heap、thread、gc(所以上面提到的其他内存异常其实也可以通过nmt来分析)。这里,我们主要关注堆外Internalmemory的内存增长。如果增长非常明显的话,那就有问题了。在细节层面,还会有特定内存段的增长,如下图所示。另外,在系统层面,我们还可以使用strace命令来监控内存分配情况strace-f-e"brk,mmap,munmap"-ppid这里的内存分配信息主要包括pid和内存地址。但实际上,以上操作很难定位到具体问题。关键是看错误日志栈,找到可疑对象,搞清楚它的恢复机制,然后分析对应的对象。比如DirectByteBuffer分配内存,需要fullGC或者手动system.gc回收(所以最好不要使用-XX:+DisableExplicitGC)。所以其实我们可以跟踪DirectByteBuffer对象的内存,通过jmap-histo:livepid手动触发fullGC,看看堆外的内存有没有被回收。如果是回收,那么很大概率是堆外内存本身分配的太小了,可以通过-XX:MaxDirectMemorySize来调整。如果没有变化,再用jmap分析不能gc的对象和DirectByteBuffer的引用关系。GC问题In-heap内存泄漏总是伴随着GC异常。但是GC问题不仅仅与内存问题相关,还可能引发CPU负载、网络问题等一系列并发症,但与内存的关系比较密切,所以我们这里将GC相关的问题单独总结一下。我们在CPU章节介绍了使用jstat获取当前GC分代变化信息。更多时候,我们使用GC日志来排查问题,在启动参数中加入-verbose:gc-XX:+PrintGCDetails-XX:+PrintGCDateStamps-XX:+PrintGCTimeStamps来启用GC日志。常见的YoungGC和FullGC日志的含义在此不再赘述。根据gclog,我们可以大致推断出youngGC和fullGC是不是过于频繁或者耗时过长,从而对症下药。下面我们将分析G1垃圾收集器,这里也推荐大家使用G1-XX:+UseG1GC。YoungGC太频繁了。YoungGC频繁是因为短周期内有很多小对象。首先考虑是否Eden区/新生代设置太小,通过调整-Xmn、-XX:SurvivorRatio等参数设置是否可以解决问题。如果参数正常,但是younggc的频率还是偏高,需要用Jmap和MAT进一步查看dump文件。youngGC耗时过长的问题取决于GC日志中时间花在了哪里。以G1日志为例,可以重点关注RootScanning、ObjectCopy、RefProc等阶段。RefProc耗时较长,所以一定要注意引用相关对象。RootScanning耗时较长,注意线程数和跨代引用。ObjectCopy需要注意对象的生命周期。而且耗时分析需要横向比较,跟其他项目或者正常时间段比较耗时。比如图中的RootScanning如果比正常时间段增加的多,说明启动的线程太多了。fullGCG1中的trigger大部分是mixedGC,但是mixedGC可以和youngGC一样检查。fullGC触发时通常会出现问题。G1会退化,使用Serial收集器来完成垃圾清理工作。停顿时间到了秒级,可以说是半跪了。fullGC的原因可能有以下几点,以及参数调整的一些思路:并发阶段失败:在并发标记阶段