概要
Java性能分析是一门艺术和科学。科学指的是性能分析通常都包括大量的数字、测量和分析;艺术指的是知识、经验和直觉的使用。性能分析的工具或者手段各有千秋,但性能的分析的过程却都截然不同。本文就已知适用的Java性能分析窍门进行一些分享,帮助用户更好的理解和运用。算法
窍门一:线程栈剖析
线程栈分析是对正在运行的Java线程的快照分析,是一种轻量级的分析手段,用户在不清楚应用存在什么性能问题的时候可优先尝试。虽然断定Java线程是否异常并无统一的标准,但用户能够经过一些指标进行定量的评估。如下分享4个检测指标:后端
1)线程死锁检查 线程死锁检查是一个很是有价值的检测指标。若是线程死锁,则通常存在系统资源的浪费或服务能力降低等问题,一旦发现就须要及时处理。线程死锁检测会展现线程死锁关系以及对应的栈信息,经过分析便可定位到触发死锁的代码。如图-1所示死锁模型展现了一个复杂的4线程死锁场景。缓存
2)线程统计检查 状态统计是对运行的线程按照运行状态进行的统计和汇总。用户在不彻底了解本身业务压力的状况下,对于可用线程数通常会配置一个很是充裕的范围,这样反而会由于过多的线程致使性能降低或者系统资源耗尽。如图-2所示,能够发现超过90%的线程处于阻塞和等待状态,那么适当优化线程数量是能够减小线程调度带来的开销以及没必要要的资源浪费。性能优化
如图-3所示,处于运行阶段的线程数已经超过90%,进一步分析可能存在线程泄露的问题。同时,运行的线程太多,线程切换的开销也是很是大的。并发
3)线程CPU使用率检查 对各Java线程CPU使用状况进行统计和排序,针对CPU使用率极高的线程线程栈进行分析,能够快速定位到程序热点。如图-4所示,首个任务线程的CPU使用率达已经到100%,则开发人员可根据业务逻辑肯定是否进行代码优化。框架
4)GC线程数检查 GC线程数每每是容易被用户忽视的指标。用户在设置并行GC线程数的时候容易忽视系统的资源状况,或者随意将应用部署在CPU核数较多的物理机。如图-5所示,咱们发如今一个4核8GB的容器中G1的并发收集线程数为9(通常状况下并行GC的线程数是GC任务线程数的1/4),也就是在GC发生的时候可能会出现9个并行的GC线程,这种状况下CPU资源会被短期直接耗尽而系统和业务阻塞。因此在使用GC收集器(如CMS、 G1)的时候尽量设置或者关注GC的线程数。socket
窍门二:GC日志剖析
日志分析是对Java程序GC收集记录数据的分析,而这部分数据的收集是须要开启特定选项的。因此,在启动Java程序前必定要增长日志参数(如JDK8:-Xloggc:logs/gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps;JDK11:-Xlog:gc*:logs/gc-%t.log:time,uptime)。GC日志分析的结果描述的是在过去一段时间内Java程序就内存回收的状态。经过分析这些状态信息,用户能够很是方便的得到到GC参数甚至Java代码优化的指标数据。如下就3个分析指标进行展开:工具
1)GC的吞吐率 吞吐率描述的是在JVM运行时间段可用于业务处理的时间占比,即非GC占用时间。该值越大表示用户GC占用的时间越少,JVM性能越好。JVM内部指定该值不能低于90%,不然JVM自己所带来的性能损耗就会严重影响业务性能。如图-6所示是JDK8的CMS运行3小时左右的日志分析结果,分析结果显示其吞吐率超过99.2%,JVM(GC)致使的性能损耗是比较低的。性能
2)暂停时间统计 GC暂停时间指的是在GC过程当中须要中止业务线程运行的时间,该时间须要在在一个合理的范围内。若是绝大部分的暂停时间超过预期(用户能够接受的范围),则颇有须要去调整GC参数以及堆大小,甚至设置并行GC线程数。如图-7所示,95%以上的GC暂停时间是在40ms之内;而超过100ms的暂停多是致使业务请求时间毛刺的主要因素。为了消除暂停时间波动问题,能够选择如G1 GC或ZGC,或者调整并行线程数或者GC参数等。测试
3)GC阶段散点图 散点图反映的是每一次GC操做释放的内存大小的分布状况。如图-8所示,每次GC释放的内存大小基本一致,说明内存释放过程比较稳定。但若是出现比较大的波动或者出现比较多的Full GC则有多是新生代区堆空间不足致使晋升量较大;若是每次GC的释放量比较少有多是G1 GC自适应算法致使的新生代空间较小等等。由于散点图展现的数据有限,因此通常须要结合其它指标以及用户的JVM参数进行联合分析。
窍门三:JFR事件剖析
JFR是Java Flight Record的缩写,是JVM内置的基于事件的JDK监控记录框架。社区中,JFR优先于OpenJDK11上发布,后移植到OpenJDK8的较高版本260上,且沿用了统一的使用接口与操做命令jcmd。同时,因为JFR录制通常对应用影响很小(默认开启的性能影响在1%之内),适合长时间开启;且JFR能收集到如Runtime、GC、线程栈、堆、IO等在内的丰富信息,很是方便用户了解Java程序的运行情况。 JFR录制的事件有100多种,若是程序复杂每每不到10分钟录制的JFR文件大小就会超过500MB,因此用户在分析时每每并非全部的信息都会关注。如下就业务性能中常见几个作一下分享:
1)进程CPU占用率 CPU采样默认间隔是1s,基本能及时反映当前进程的CPU平均使用状况。在出现CPU持续偏高,或者CPU出现相似图-9所示的CPU偶发飙高的状况时,均可以进行必定的检测和分析。经过进一步定位,此处CPU飙高与GC触发时间一致,初步确认是GC致使的CPU变化。
2)GC配置及暂停分布 GC配置能够帮助咱们了解到当前进程的GC收集器及其主要的配置参数,由于不用的收集器特性会不一样,分析堆空间、触发控制等参数也是很是重要的。控制参数能够帮助咱们理解GC收集过程,好比图-10所示的G1收集器,设置的最大堆为8GB,GC暂停时间在40ms左右(默认预期200ms),是远低于预期值的。进一步分析参数发现设置了NewRatio值为2(对G1 GC不太熟悉的状况下,用户很容易设置该参数),致使新生代区的GC触发频繁,并且从数据看未触发混合GC。为了增长对堆空间的利用,能够移除NewRatio参数,增长新生代区的最大值比例(由于未触发混合GC,说明堆回收时晋升量很是低),下降回收块的回收门槛等,进而增长对整堆的使用。经过优化,堆空间的使用从原来的4GB提升到7GB,YGC频率从20s/次提升到平均40s/次,GC暂停时间没有明显变化。
3)方法采样火焰图 方法火焰图是对调用方法采样次数的统计,比例越大表示调用次数越多。由于采样过程当中有栈的完整信息,对于用户来讲是很是比较直观的,性能优化的帮助性大增。如图-11所示,能够很清楚的看到GroupHeap.match执行次数比例接近30%,能够做为性能优化点。
4)IO读写性能 检查IO性能多半是对程序处理性能出现突变的场景,好比降低或飙升。如从socket读入的数据量飙升,致使处理业务的CPU飙高;或者由于须要写出的数据变多,致使业务线程阻塞,处理能力降低等。如图-11所示,能够经过读取/写入趋势图判断在监控时间段的IO能力。
窍门四:堆内容剖析
堆内容分析是分析Java堆OOM(OutOfMemoryError)缘由的经常使用手段。OOM主要有堆空间溢出、元空间溢出、栈空间溢出和直接内存溢出等,但并非全部溢出状况均可以经过堆内容分析得到。对于堆转储的文件而言,内存溢出的可能性是不肯定的,但能够经过一些定量的指标或者约定的条件做出判断,再经过开发或者测试人员进行最后的确认。如下分享三个有价值的衡量指标:
1)大对象检查 统计大对象分布信息能够帮助咱们了解内存消耗在这部分对象上的比重,以及存在的大对象是否合理。过多的大对象没法释放会更快的耗尽内存而出现OOM,相比全量的分析全部对象而言,大对象的检查是具备表明性的,如图-13所示。
2)类加载检查 类加载统计主要统计的是程序当前加载的所有类信息,是计算元空间占用的重要数据。过多的加载类信息也会致使元空间被大量占用,在相似RPC场景下,缓存加载类信息是容易触发OOM的。
3)对象泄露检查 首先引入三个概念; 浅堆:一个对象所占用的内存大小,和对象的内容无关,只和对象的结构有关。 深堆:一个对象被GC回收后,能够真实释放的内存大小,即经过该对象访问到的全部对象的浅堆之和(支配树)。 支配树:在对象的引用图中,全部指向对象B的路径都通过对象A,则认为对象A支配对象B;若是对象A是离对象B最近的一个支配对象,则认为对象A为对象B的直接支配者。 按照GC策略,堆中的对象只可能有两种状态,一种是经过GC的根可达的对象;另外一种是经过GC根不可达的对象。不可达的对象会被GC收集器回收,对应的内存就会返回到系统中去。而可达对象都是被用户直接或者间接引用的对象,因此对象泄露针对的就是被用户间接引用但永远不会被使用的对象,这些对象由于被引用而没法释放。对象泄露不是绝对的,而是相对的,通常没有确切的标准,但能够经过对对象的深堆大小进行评估。好比检测到HashMap存放了4844个对象(如图-14所示),计算HashMap浅堆约115KB,看到这里可能以为没有什么问题;但经过计算对象的深堆发现其超过500MB。这种状况下,若是没法释放HashMap而持续增长新的键值就有可能致使堆内存耗尽而出现OOM。
做者简介:
Nianwu,高级后端工程师
主要负责Java性能平台和JDK支持,对缺陷检查和编译器也有深刻研究。
获取更多精彩内容,请关注[OPPO互联网技术]公众号