一、问题解析
在软件开发过程中,排查和修复产线问题是每⼀位⼯程师都需要掌握的基本技能。但是在⽣产环境中,
程序代码、硬件、⽹络、协作软件等任⼀因素,都会引发意想不到的问题,所以排查产线问题⽐较困
难,所以问题的定位体现了⼀名⼯程师的基础能⼒,问题的解决则体现了⼯程师的技能素养。
以下从5个⽅⾯分享产线常⻅问题的排查⼿段。
- Java常⻅线上问题总结
- 如何定位问题
- APM链路跟踪分析
- 常⽤Linux分析命令
- Arthas(阿尔萨斯)诊断命令
- JVM问题定位命令
- GC分析
33.1 Java常⻅线上问题总结
绝⼤多数Java线上问题从表象来看通常可以归纳为4个⽅⾯:CPU、内存、磁盘、⽹络。⽐如,应⽤上线
后突然CPU使⽤率99%、内存泄漏、STW时间过⻓,这些问题通常可以分为两⼤类:
系统异常 (CPU占⽤率过⾼、磁盘使⽤率100%、系统可⽤内存低等)
业务异常 (服务运⾏⼀段时间⾃动退出、服务间调⽤时间过⻓、多线程并发异常、死锁等)
33.2 如何定位问题
解决问题的第⼀步是定位问题,因为只有定位到了问题产⽣的原因,才能准确的抉择出解决⽅案,排查
⼿段⼀般包括以下⼏项,也可以将此理解为排查顺序:
1. 业务⽇志分析排查
2. APM分析排查
3. 外部环境排查
4. 应⽤服务排查
5. 云⼚商或运营商问题排查
33.2.1 业务⽇志分析排查
通常情况下,⼤部分错误信息都会在⽇志上有所体现,⽐如看看下⾯这段代码
这段代码使⽤了 并发流 将数据加⼊到⼀个List中,再运⾏过程中,抛出了
ArrayIndexOutOfBoundsException ,具体异常栈信息如下:
通过⽇志可以看到出错的位置在 TaskServiceImpl.java ⽂件的第75⾏代码上,错误信息
是 java.lang.ArrayIndexOutOfBoundsException:163 ,这就是问题定位,整体描述出来就是: 在
TaskServiceImpl.java⽂件中第75⾏代码上出现了数组越界异常,引起异常的原因是在调⽤
java.util.ArrayList#add()⽅法时因为并发请求导致没有动态扩展数组容量 。
为什么我们能够直接描述出来是在调⽤ ArrayList#add() ⽅法时因为并发请求导致的没有动态扩展数
组容量呢?因为我们结合ArrayList的内部实现原理及动态扩容的特性,可以知道在单线程的情况下,代
码是串⾏执⾏,ArrayList内的数组都会是先扩容再插⼊,所以不会出现数组越界的错误,既然单线程不
会引起该错误,那⼀定就是多线程并发造成的了,解决⽅案就是给代码加锁或者由并发改串⾏,就是这
么简单,这就是技能素养的体现。
通过上⾯的分析,教育了我们:⼀定要在关键代码逻辑位置输出相关⽇志,尤其是在代码发⽣异常的时
候,定要将⽇志输出到⽂件中,只有这样,才更利于我们的排查。
33.2.2 APM分析排查
APM,全称Application Performance Management,应⽤性能管理,⽬的是通过各种探针采集数据,
收集关键指标,同时搭配数据呈现以实现对应⽤程序性能管理和故障管理的系统化解决⽅案。通过分布
式链路调⽤跟踪系统,通过在系统请求中透传 trace-id,将所有相关⽇志进⾏聚合,然后⽇志统⼀采集
和分析后,以图形化的形式展示给⼯程师们,⽽他们在排查问题的时候,可以简单粗暴且直观的调度出
问题最根本的原因。
业务⽇志较优于解决单体服务异常,但现在的应⽤通常使⽤的是分布式架构,⽽在分布式系统中,仅通
过单服务节点的⽇志,很难将错误信息与请求链路关联在⼀起,也就是通过系统中某个服务的异常⽇志
信息很难定位到问题的根本原因。并且我们还需要关注各服务执⾏过程中的耗时情况,及时的发现异常
服务,并根据异常信息进⾏修复。要满⾜这种需求,仅通过分析单个服务的⽇志信息是不够的,此时则
需要APM进⾏全链路分析,通过请求链路监控,实时的发现链路中相关服务的异常情况。
⽐如我们使⽤Skywalking,进⼊到Skywalking的控制台查看的链路信息就是这样的:
⽬前市场上使⽤较多的链路跟踪⼯具有如下⼏个:
Apache Skywalking:Apache SkyWalking
Pinpoint:https://pinpoint.com/product/for-engineers
SpringCloud Zipkin:https://docs.spring.io/spring-cloud-sleuth/docs/current-SNAPSHOT/referenc
e/html/#sending-spans-to-zipkin
33.2.3 物理环境排查
物理环境是指⼯程所依附的物理环境,⽐如服务器、宿主机、容器等,细分为服务器负载、CPU、内
存、磁盘、⽹络⼏个⽅⾯。
33.2.3.1 CPU分析
排查CPU的⽬的主要是查看服务器CPU的使⽤率, 使⽤top命令分析CPU使⽤情况
33.2.3.2 内存分析
使⽤free -m命令查看内存使⽤情况
33.2.3.3 磁盘分析
使⽤df -h、iostat、lsof等命令查看磁盘IO情况,找到读写异常的进程
33.2.3.4 ⽹络分析
使⽤dstat、vmstat等命令查看⽹络流量、TCP连接等情况,分析异常流量
33.2.4 应⽤服务排查
应⽤排查,排查应⽤本身最有可能引发的问题,针对各种场景进⾏对应分析
33.2.4.1 CPU分析
使⽤jstack等命令进⾏JVM分析
33.2.4.2 内存分析
使⽤jmap等命令分析内存使⽤情况
33.2.4 云⼚商或运营商问题排查
排查到了这⼀步的话,只需关注云⼚商或运营商官⽅公告即可。
假设我们从业务⽇志、APM分析中都没分析出问题所在,那么此时基本只能连接到⽣产环境中进⾏排查
了。根据上⾯的外部排查⽅案,这⾥简单复习下常⽤的Linux分析命令以便从系统层⾯上分析。
33.3 常⽤Linux分析命令
针对外部环境排查,需要使⽤的常⽤Linux分析命令包括 : top、free、df、lsof、dstat、vmstat等,简
单介绍如下:
33.3.1 CPU
CPU使⽤率是衡量系统繁忙程度的重要指标。但是CPU使⽤率的安全阈值是相对的,取决于你的系统的
IO密集型还是计算密集型。⼀般计算密集型应⽤CPU使⽤率偏⾼load偏低,IO密集型相反。
top命令是Linux下常⽤的 CPU 性能分析⼯具,能够实时显示系统中各个进程的资源占⽤状况,常⽤于服
务端性能分析。
top
👋
top 命令显示了各个进程 CPU 使⽤情况,⼀般 CPU 使⽤率从⾼到低排序展示输出。其中 Load
Average 显示最近1分钟、5分钟和15分钟的系统平均负载,上图各值为3.4、3.31、3.46。
我们⼀般会关注 CPU 使⽤率最⾼的进程,正常情况下就是我们的应⽤主进程。第七⾏以下:各进
程的状态监控,参数说明:
PID : 进程id
USER : 进程所有者的⽤户名
PR : 进程优先级
NI : nice值。负值表示⾼优先级,正值表示低优先级
VIRT : 进程使⽤的虚拟内存总量,单位kb
SHR : 共享内存⼤⼩
%CPU : 上次更新到现在的CPU时间占⽤百分⽐
%MEM : 进程使⽤的物理内存百分⽐
TIME+ : 进程使⽤的CPU时间总计,单位1/100秒
COMMAND : 命令名称、命令⾏
33.3.2 内存
内存是排查线上问题的重要参考依据,free 是显示的当前内存的使⽤,-h 表示⼈类可读性
free -h
💡
参数说明:
total :内存总数
used:已经使⽤的内存数
free:空闲的内存数
shared:被共享使⽤的物理内存⼤⼩
buffers/buffer:被 buffer 和 cache 使⽤的物理内存⼤⼩
available: 还可以被应⽤程序使⽤的物理内存⼤⼩
33.3.3 磁盘
磁盘满了很多时候会引发系统服务不可⽤等问题
df -h
33.3.4 ⽹络
dstat 是⼀个可以取代vmstat,iostat,netstat和ifstat这些命令的多功能产品。
dstat
👋
默认情况下,dstat每秒都会刷新数据。需要注意的是报告的第⼀⾏,由于dstat会通过上⼀次的报
告来给出⼀个总结,所以第⼀次运⾏时是没有平均值和总值的相关数据。
默认输出解释:
CPU状态:显示了⽤户,系统和空闲部分,便于分析CPU当前的使⽤状况
磁盘统计:磁盘的读写操作,显示磁盘的读、写总数。
⽹络统计:⽹络设备发送和接受的数据,显示了⽹络收、发数据总数。
分⻚统计:系统的分⻚活动。
系统统计:这⼀项显示的是中断(int)和上下⽂切换(csw)。
到了这⼀步,如果CPU、内存、磁盘、⽹络这四个地⽅都没有问题的话,那就进⼊了应⽤本身的问题排
查阶段。下⼀步就该使⽤jvm命令进⾏问题定位了,但很多研发可能由于⾃身⼯作经验不⾜、对Java内存
模型理解不深、尚未掌握Jvm排查命令等原因对Jvm相关排查畏⼿畏脚,不够⾃信,进⽽影响排查进度。
针对这种情况,本⽂推荐⼀款开源的Java诊断⼯具,对Jvm不熟的研发可以尝试学习使⽤下。相对Jvm命
令来说简单多了
33.4 Arthas诊断命令
Arthas 是Alibaba开源的Java诊断⼯具,深受开发者喜爱。
当你遇到以下类似问题⽽束⼿⽆策时,Arthas 可以帮助你解决
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执⾏到?难道是我没 commit?分⽀搞错了?
- 遇到问题⽆法在线上 debug,难道只能通过加⽇志再重新发布吗?
- 线上遇到某个⽤户的数据处理有问题,但线上同样⽆法 debug,线下⽆法重现!
- 是否有⼀个全局视⻆来查看系统的运⾏状况?
- 有什么办法可以监控到JVM的实时运⾏状态?
- 怎么快速定位应⽤的热点,⽣成⽕焰图?
Arthas⽀持JDK 6+,⽀持Linux、Mac、Winodws,采⽤命令⾏交互模式,同时提供丰富的 Tab ⾃动补全功能,进⼀步⽅便进⾏问题的定位和诊断。
33.4.1 下载安装
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
33.4.2 启动Arthas
java -jar arthas-boot.jar
arthas 会列出已存在的Java进程,并提醒输入序号,键入回车,进入arthas 诊断界面。
33.4.3 开始诊断
按照提醒,这里输入需要诊断的序号,输入回车,响应界面如下:
33.4.4 查看dashboard
输入dashboard【支持Tab补全命令】,输入回车,会展示当前系统的实时面板,按ctrl+c可以中断执 行。
上图线上了当前系统对应的线程 、内存(分代) 、GC 、Runtime的各项信息。
除了dashboard命令外, arthas还有许多常用命令,这里挑一些做简单说明。
33.4.5 arthas常见命令介绍
- jvm 查看当前 JVM 的信息
- thread 查看当前 JVM 的线程堆栈信息, -b选项可以一键检测死锁
- trace 方法内部调用路径,并输出方法路径上的每个节点上耗时,服务间调用时间过长时使用
- stack 输出当前方法被调用的调用路径
- Jad 反编译指定已加载类的源码,反编译便于理解业务
- logger 查看和修改logger,可以动态更新日志级别。
这里只列出常用命令,完整列表参考命令列表: https://alibaba.github.io/arthas/commands.ht ml 。
arthas使用上相对JVM命令简单很多,即便是工作年限不多的小伙伴学起来也很快。熟练使用arthas应 该能诊断出大部分线上应用问题,但生产环境通常不允许擅自拷贝jar包,而且arthas会拖慢应用本身, 如果条件不允许,又该如何诊断呢?这边简单介绍下jdk自带的命令行工具。
33.5 JVM问题定位命令
在 JDK 安装目录的bin 目录下默认提供了很多有价值的命令行工具。每个小工具体积基本都比较小,因 为这些工具只是 jdk\lib\tools.jar 的简单封装。
其中,定位排查问题时最为常用命令包括: jps(进程)、jmap(内存)、jstack(线程)、jinfo(参 数)等。
- jps:查询当前机器所有Java进程信息
- jmap:输出某个 Java 进程内存情况
- jstack:打印某个 Java 线程的线程栈信息
- jinfo:用于查看jvm 的配置参数
33.5.1 jps
jps 用于输出当前用户启动的所有进程 ID,当线上发现故障或者问题时,利用jps 快速定位对应的 Java 进程 ID。
📌
参数解释:
-m :输出传入 main 方法的参数
-l:输出完全的包名,应用主类名, jar的完全路径名
jps -m
当然,我们也可以使用 Linux 提供的查询进程状态命令也能快速获取Tomcat 服务的进程id。比如:
ps -ef | grep tomcat
33.5.2 jmap
jmap(Java Memory Map)可以输出所有内存中对象的工具,甚至可以将VM 中的heap,以二进制输
出成文本,使用方式如下: jmap -heap:
jmap -heap pid 输出当前进程JVM堆内存新生代、老年代、持久代、 GC算法等信息
注意: pid 通过jps命令得知
jmap -histo:
jmap -histo:live pid | head -n 20 输出当前进程内存中所有对象包含的大小
输出当前进程内存中所有对象实例数(instances)和大小(bytes),如果某个业务对象实例数和大小 存在异常情况,可能存在内存泄露或者业务设计方面存在不合理之处。
jmap -dump:
jmap -dump:format=b,file=/apps/logs/gc/dump.hprof {pid}
以二进制形式输出当前内存的堆情况,便于导入MAT等工具进行分析情况
一般我们要求给 JVM 添加参数-XX:+Heap Dump On Out Of Memory Error OOM 确保应用发生
OOM 时 JVM 能够保存并dump 出当前的内存镜像。当然如果你决定手动 dump 内存时, dump 操作占 据一定CPU 时间片、内存资源、磁盘资源等,因此会带来一定的负面影响。
此外, dump 的文件可能比较大,一般我们可以考虑使用zip命令对文件进行压缩处理,这样在下载文件 时能减少带宽的开销。在下载dump 文件完成之后,由于 dump 文件较大可将dump 文件备份至制定 位置或者直接删除,以释放磁盘在这块的空间占用。
33.5.3 jstack
jstack用于打印某个 Java 线程的线程栈信息,通常用以下三步分析:
printf '%x\n' tid 10进制至16进制线程转换
jstack pid | grep tid -C 30 --color
ps -mp 8278 -o THREAD,tid,time | head -n 40
举个栗子,某 Java 进程CPU 占用率高,我们想要定位到其中CPU 占用率最高的线程,如何定位?
1、利用 top 命令可以查出占CPU 最高的线程 pid
top -Hp pid
2 、 占用率最高的线程ID 为22021,将其转换为16进制形式(因为 java native 线程以16进制形式输 出)
printf '%x\n' 22021
3 、利用jstack 打印出 Java 线程调用栈信息
jstack 21993 | grep '0x5605' -A 50 --color
33.5.4 jinfo
jinfo可以用来查看正在运行的 java 应用程序的扩展参数,包括Java System属性和JVM命令行参数;也 可以动态的修改正在运行的 JVM 一些参数。
jinfo pid
33.5.5 jstat
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。
jstat -gc pid
33.5.6 内存分析工具MAT
MAT(Memory Analyzer Tool),一个基于 Eclipse 的内存分析工具,是一个快速、功能丰富的 JAVA heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行 分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报 表直观的查看到可能造成这种结果的对象。
右侧的饼图显示当前快照中最大的对象。单击工具栏上的柱状图,可以查看当前堆的类信息,包括类的 对象数量、浅堆(Shallow heap)、深堆(Retained Heap)。
浅堆表示一个对象结构所占用内存的大小。 深堆表示一个对象被回收后,可以真实释放的内存大小。
- 支配树(The Dominator Tree):
列出了堆中最大的对象,第二层级的节点表示当被第一层级的节点所引用到的对象,当第一层级对 象被回收时,这些对象也将被回收。这个工具可以帮助我们定位对象间的引用情况,垃圾回收时候 的引用依赖关系
● Path to GC Roots
被JVM持有的对象,如当前运行的线程对象,被systemclass loader加载的对象被称为GC Roots, 从一个对象到GC Roots的引用链被称为Path to GC Roots, 通过分析Path to GC Roots可以找出 JAVA的内存泄露问题,当程序不在访问该对象时仍存在到该对象的引用路径。
33.6 GC分析
33.6.1 GC 日志分析
Java 虚拟机GC日志是用于定位问题重要的日志信息,频繁的GC将导致应用吞吐量下降、响应时间增 加,甚至导致服务不可用。
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/apps/logs/gc/gc.log -
XX:+UseConcMarkSweepGC
我们可以在 Java 应用的启动参数中增加-XX:+PrintGCDetails 可以输出GC 的详细日志,例外还可以增 加其他的辅助参数,如-Xloggc 制定GC 日志文件地址。如果你的应用还没有开启该参数,下次重启时请 加入该参数。
上图为线上某应用在平稳运行状态下的GC日志截图。
2017-12-29T18:25:22.753+0800: 73143.256: [GC2017-12-29T18:25:22.753+0800:
73143.257: [ParNew: 559782K->1000K(629120K), 0.0135760 secs] 825452K-
>266673K(2027264K), 0.0140300 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] [2017-12-29T18:25:22.753+0800: 73143.256] : 自JVM启动73143.256秒时发生本次GC.
[ParNew: 559782K->1000K(629120K), 0.0135760 secs] : 对新生代进行的GC,使用 ParNew收 集器, 559782K是新生代回收前的大小,1000K是新生代回收后大小,629120K是当前新生代分配的内存总大 ⼩ , 0.0135760 secs表示本次新生代回收耗时 0.0135760秒
[825452K->266673K(2027264K), 0.0140300 secs]:825452K是回收堆内存大小,266673K是回收 堆之后内存大小, 2027264K是当前堆内存总大小,0.0140300 secs表示本次回收共耗时0.0140300秒
[Times: user=0.02 sys=0.00, real=0.02 secs] : 用户态耗时0.02秒 ,系统态耗时0.00,实际耗 时0.02秒
无论是 minor GC 或者是 Full GC,我们主要关注 GC 回收实时耗时, 如real=0.02secs,即 stop the world 时间,如果该时间过长,则严重影响应用性能。
33.6.2 CMS GC 日志分析
Concurrent Mark Sweep(CMS)是老年代垃圾收集器,从名字(Mark Sweep)可以看出, CMS 收集 器就是“标记-清除”算法实现的,分为六个步骤:
- 初始标记(STW initial mark)
- 并发标记(Concurrent marking)
- 并发预清理(Concurrent precleaning)
- 重新标记(STW remark)
- 并发清理(Concurrent sweeping)
- 并发重置(Concurrent reset)
其中初始标记(STW initial mark) 和重新标记(STW remark)需要“Stop the World”。
初始标记 :在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽 然暂停了整个 JVM,但是很快就完成了。
并发标记 :这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用 程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。
并发预清理 :并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年 代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫 描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World。
重新标记 :这个阶段会暂停虚拟机,收集器线程扫描在 CMS 堆中剩余的对象。扫描从"跟对象"开始向下 追溯,并处理对象关联。
并发清理 :清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。
并发重置 :这个阶段,重置 CMS 收集器的数据结构,等待下一次垃圾回收。
CMS 使得在整个收集的过程中只是很短的暂停应用的执行,可通过在 JVM 参数中设置-
XX:UseConcMarkSweepGC 来使用此收集器,不过此收集器仅用于old 和Perm(永生)的对象收集。 CMS 减少了stop the world 的次数,不可避免地让整体GC 的时间拉长了。
Full GC 的次数说的是stop the world 的次数,所以一次CMS 至少会让 Full GC 的次数+2,因为 CMS Initial mark 和remark 都会stop the world,记做2次。而 CMS 可能失败再引发一次Full GC。
上图为线上某应用在进行 CMS GC 状态下的GC 日志截图。
2017-11-02T09:27:03.989+0800: 558115.552: [GC [1 CMS-initial-mark:
1774783K(1926784K)] 1799438K(2068800K), 0.0123430 secs] [Times: user=0.01
sys=0.01, real=0.02 secs]
2017-11-02T09:27:04.001+0800: 558115.565: [CMS-concurrent-mark-start]
2017-11-02T09:27:04.714+0800: 558116.277: [CMS-concurrent-mark: 0.713/0.713
secs] [Times: user=1.02 sys=0.03, real=0.71 secs]
2017-11-02T09:27:04.714+0800: 558116.277: [CMS-concurrent-preclean-start]
2017-11-02T09:27:04.722+0800: 558116.285: [CMS-concurrent-preclean: 0.008/0.008
secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2017-11-02T09:27:04.722+0800: 558116.286: [CMS-concurrent-abortable-preclean-
start]
2017-11-02T09:27:04.836+0800: 558116.399: [GC2017-11-02T09:27:04.836+0800:
558116.400: [ParNew: 138301K->6543K(142016K), 0.0155540 secs] 1913085K-
>1781327K(2068800K), 0.0160610 secs] [Times: user=0.03 sys=0.01, real=0.02
secs]
2017-11-02T09:27:05.005+0800: 558116.569: [CMS-concurrent-abortable-preclean:
0.164/0.283 secs] [Times: user=0.46 sys=0.02, real=0.28 secs]
2017-11-02T09:27:05.006+0800: 558116.570: [GC[YG occupancy: 72266 K (142016
K)]2017-11-02T09:27:05.006+0800: 558116.570: [Rescan (parallel) , 0.2523940
secs]2017-11-02T09:27:05.259+0800: 558116.822: [weak refs processing, 0.0011240
secs]2017-11-02T09:27:05.260+0800: 558116.823: [scrub string table, 0.0028570
secs] [1 CMS-remark: 1774783K(1926784K)] 1847049K(2068800K), 0.2566410 secs]
[Times: user=0.14 sys=0.00, real=0.26 secs]
2017-11-02T09:27:05.265+0800: 558116.829: [CMS-concurrent-sweep-start]
2017-11-02T09:27:05.422+0800: 558116.986: [GC2017-11-02T09:27:05.423+0800:
558116.986: [ParNew: 120207K->2740K(142016K), 0.0179330 secs] 1885446K-
>1767979K(2068800K), 0.0183340 secs] [Times: user=0.03 sys=0.01, real=0.02
secs]
2017-11-02T09:27:06.240+0800: 558117.804: [GC2017-11-02T09:27:06.240+0800:
558117.804: [ParNew: 116404K->3657K(142016K), 0.0134680 secs] 1286444K-
>1173697K(2068800K), 0.0138460 secs] [Times: user=0.03 sys=0.00, real=0.01
secs]
2017-11-02T09:27:06.966+0800: 558118.530: [GC2017-11-02T09:27:06.966+0800:
558118.530: [ParNew: 117321K->2242K(142016K), 0.0135210 secs] 738838K-
>623759K(2068800K), 0.0140130 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
2017-11-02T09:27:07.144+0800: 558118.708: [CMS-concurrent-sweep: 1.820/1.879
secs] [Times: user=2.88 sys=0.14, real=1.88 secs]
2017-11-02T09:27:07.144+0800: 558118.708: [CMS-concurrent-reset-start]
2017-11-02T09:27:07.149+0800: 558118.713: [CMS-concurrent-reset: 0.005/0.005
secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
如果你已掌握CMS 的垃圾收集过程,那么上面的GC 日志你应该很容易就能看的懂,这里我就不详细展 开解释说明了。
此外CMS 进行垃圾回收时也有可能会发生失败的情况。
异常情况有:
伴随prommotion failed,然后 Full GC:
[prommotion failed:存活区内存不足,对象进入老年代,而此时老年代也仍然没有内存容纳对象,将 导致一次Full GC]
伴随concurrent mode failed,然后 Full GC:
[concurrent mode failed :CMS回收速度慢, CMS完成前,老年代已被占满,将导致一次Full GC]
频繁CMS GC:
[内存吃紧,老年代长时间处于较满的状态]
33.7 常见问题分析、解答
问: 生产环境里某个节点里的容器里面服务挂了,但是在预生产环境无法重现,而生产环境监控里面显 示cpu、内存、堆栈都正常。而那个节点重启之后就正常,但是过段时间又出现问题。这种情况有什么 好的方法排查问题?
答: 这种情况首先需要排查内核版本,排查tomcat版本,可能自身应用存在bug,真的没办法可以考虑 降低日志等级,打印出更为详细有价值的启动/业务日志信息。
问:线上排查问题dump 或 jstack 导出文件分析经常无响应导致无法导出查看内存问题和线程问题,有 什么办法吗?
答: 增加-F 强制,在 jmap-dump 使用,如果pid 没有相应的回复。当然-F 也可能没办法dump 下 来,那就只能去分析日志了。当然如果你决定手动dump 内存时, dump 操作占据一定CPU 时间片、 内存资源、磁盘资源等,对系统的正常的运行可能会有一些影响。
33.8 案例分析
某工程消费ActiveMQ消息,该工程整合了Apache Camel,经过运维排查定位到Camel拉取到了消息, 但收到消息后打印日志时却延迟了十几分钟, Camel配置了MySQL用于校验幂等,一些列初步排查下 来,未找到原因,该如何排查?
线上问题出错,而且只有线上环境能复现问题,在运维人员同意的情况下,将Alibaba Arthas拷贝进容器中,启动Arthas,开始跟踪,由于是Springboot工程,并且Camel作为消息入口,故先从配置开始跟 踪,详细排查问题如下:
33.8.1 延迟场景,首先排查有无死锁产生
thread -b
仔细分析下来并不是死锁,那就从入口开始。
33.8.2 从Camel配置开始跟踪
trace --skipJDKMethod true org.apache.camel.builder.RouteBuilder configure
'#cost > 20' -n 10
注意: skipJDKMethod:跟踪结果不包含JDK方法。
开始构造延迟场景,但未能跟踪到相关信息。
于是调整策略,从消息派发器开始跟踪。
33.8.3 跟踪统一消息派发器MessageDispatcher
trace com.test.messagedispatcher.MessageDispatcher onMessage -n 10
开始构造延迟场景,也没能跟踪到与延迟相关的信息,继续调整方向,下载了Apache Camel源代码,开 始分析从JMS 消费者开始分析Camel核心入口方法,定位到创建监听器代码:
protected void createMessageListener(JmsEndpoint endpoint, Processor processor)
{
messageListener = new EndpointMessageListener(endpoint, processor);
getEndpoint().getConfiguration().configureMessageListener(messageListener);
messageListener.setBinding(endpoint.getBinding());
messageListener.setAsync(endpoint.getConfiguration().isAsyncConsumer());
}
33.8.4 分析Camel创建监听器方法
trace org.apache.camel.component.jms.EndpointMessageListener *
继续构造延迟场景,结果如下:
上图红色部分出现了930027ms,初步定位到了延迟发生在Camel中间件,继续跟踪
33.8.5 跟踪AsyncProcessor:process方法
trace org.apache.camel.AsyncProcessor process
继续构造延迟场景,跟踪结果如下、
原来是幂等消费阻塞了几分钟,最后的关键信息是,继续跟踪。
33.8.6 跟踪IdempotentRepository:add方法
trace org.apache.camel.spi.IdempotentRepository add
原来是数据库事务的问题,继续跟踪。
33.8.7 直至定位到原因
原来是抓取JDBC连接的时候阻塞了
二、粉丝福利
我根据我从小白到架构师多年的学习经验整理出来了一份50W字面试解析文档、简历模板、学习路线图、java必看学习书籍 、 需要的小伙伴斯我“159”,或者评论区扣“求分享”