90%的人会遇到性能问题,如何用1行代码快速定位?

点击这里,查看常见内存分析思路及其余重要内容前端

简介:今天,齐光将会基于以前列举的众多指标,给出一些常见的调优分析思路,即:如何在众多异常性能指标中,找出最核心的那一个,进而定位性能瓶颈点,最后进行性能调优。整篇文章会按照代码、CPU、内存、网络、磁盘等方向进行组织,针对对某一各优化点,会有系统的「套路」总结,便于思路的迁移实践。ios

1. 代码相关

遇到性能问题,首先应该作的是检查否与业务代码相关——不是经过阅读代码解决问题,而是经过日志或代码,排除掉一些与业务代码相关的低级错误。性能优化的最佳位置,是应用内部。正则表达式

譬如,查看业务日志,检查日志内容里是否有大量的报错产生,应用层、框架层的一些性能问题,大多数都能从日志里找到端倪(日志级别设置不合理,致使线上疯狂打日志);再者,检查代码的主要逻辑,如 for 循环的不合理使用、NPE、正则表达式、数学计算等常见的一些问题,均可以经过简单地修改代码修复问题。设计模式

别动辄就把性能优化和缓存、异步化、JVM 调优等名词挂钩,复杂问题可能会有简单解,「二八原则」在性能优化的领域里里依然有效。固然了,了解一些基本的「代码经常使用踩坑点」,能够加速咱们问题分析思路的过程,从 CPU、内存、JVM 等分析到的一些瓶颈点优化思路,也有可能在代码这里体现出来。缓存

下面是一些高频的,容易形成性能问题的编码要点。性能优化

1)正则表达式很是消耗 CPU(如贪婪模式可能会引发回溯),慎用字符串的 split()、replaceAll() 等方法;正则表达式表达式必定预编译。网络

2)String.intern() 在低版本(Java 1.6 以及以前)的 JDK 上使用,可能会形成方法区(永久代)内存溢出。在高版本 JDK 中,若是 string pool 设置过小而缓存的字符串过多,也会形成较大的性能开销。并发

3)输出异常日志的时候,若是堆栈信息是明确的,能够取消输出详细堆栈,异常堆栈的构造是有成本的。注意:同一位置抛出大量重复的堆栈信息,JIT 会将其优化后成,直接抛出一个事先编译好的、类型匹配的异常,异常堆栈信息就看不到了。框架

4)避免引用类型和基础类型之间无谓的拆装箱操做,请尽可能保持一致,自动装箱发生太频繁,会很是严重消耗性能。dom

5)Stream API 的选择。复杂和并行操做,推荐使用 Stream API,能够简化代码,同时发挥来发挥出 CPU 多核的优点,若是是简单操做或者 CPU 是单核,推荐使用显式迭代。

6)根据业务场景,经过 ThreadPoolExecutor 手动建立线程池,结合任务的不一样,指定线程数量和队列大小,规避资源耗尽的风险,统一命名后的线程也便于后续问题排查。

7)根据业务场景,合理选择并发容器。如选择 Map 类型的容器时,若是对数据要求有强一致性,可以使用 Hashtable 或者 「Map + 锁」 ;读远大于写,使用 CopyOnWriteArrayList;存取数据量小、对数据没有强一致性的要求、变动不频繁的,使用 ConcurrentHashMap;存取数据量大、读写频繁、对数据没有强一致性的要求,使用 ConcurrentSkipListMap。

8)锁的优化思路有:减小锁的粒度、循环中使用锁粗化、减小锁的持有时间(读写锁的选择)等。同时,也考虑使用一些 JDK 优化后的并发类,如对一致性要求不高的统计场景中,使用 LongAdder 替代 AtomicLong 进行计数,使用 ThreadLocalRandom 替代 Random 类等。

代码层的优化除了上面这些,还有不少就不一一列出了。咱们能够观察到,在这些要点里,有一些共性的优化思路,是能够抽取出来的,譬如:

空间换时间:使用内存或者磁盘,换取更宝贵的CPU 或者网络,如缓存的使用;
时间换空间:经过牺牲部分 CPU,节省内存或者网络资源,如把一次大的网络传输变成屡次;
其余诸如并行化、异步化、池化技术等。

2. CPU 相关

前面讲到过,咱们更应该关注 CPU 负载,CPU 利用率高通常不是问题,CPU 负载 是判断系统计算资源是否健康的关键依据。

2.1 CPU 利用率高&&平均负载高

这种状况常见于 CPU 密集型的应用,大量的线程处于可运行状态,I/O 不多,常见的大量消耗 CPU 资源的应用场景有:

正则操做
数学运算
序列化/反序列化
反射操做
死循环或者不合理的大量循环
基础/第三方组件缺陷

排查高 CPU 占用的通常思路:经过 jstack 屡次(> 5次)打印线程栈,通常能够定位到消耗 CPU 较多的线程堆栈。或者经过 Profiling 的方式(基于事件采样或者埋点),获得应用在一段时间内的 on-CPU 火焰图,也能较快定位问题。

还有一种可能的状况,此时应用存在频繁的 GC (包括 Young GC、Old GC、Full GC),这也会致使 CPU 利用率和负载都升高。排查思路:使用 jstat -gcutil 持续输出当前应用的 GC 统计次数和时间。频繁 GC 致使的负载升高,通常还伴随着可用内存不足,可用 free 或者 top 等命令查看下当前机器的可用内存大小。

CPU 利用率太高,是否有多是 CPU 自己性能瓶颈致使的呢?也是有可能的。能够进一步经过 vmstat 查看详细的 CPU 利用率。用户态 CPU 利用率(us)较高,说明用户态进程占用了较多的 CPU,若是这个值长期大于50%,应该着重排查应用自己的性能问题。内核态 CPU 利用率(sy)较高,说明内核态占用了较多的 CPU,因此应该着重排查内核线程或者系统调用的性能问题。若是 us + sy 的值大于 80%,说明 CPU 可能不足。

2.2 CPU 利用率低&&平均负载高

若是CPU利用率不高,说明咱们的应用并无忙于计算,而是在干其余的事。CPU 利用率低而平均负载高,常见于 I/O 密集型进程,这很容易理解,毕竟平均负载就是 R 状态进程和 D 状态进程的和,除掉了第一种,就只剩下 D 状态进程了(产生 D 状态的缘由通常是由于在等待 I/O,例如磁盘 I/O、网络 I/O 等)。

排查&&验证思路:使用 vmstat 1 定时输出系统资源使用,观察 %wa(iowait) 列的值,该列标识了磁盘 I/O 等待时间在 CPU 时间片中的百分比,若是这个值超过30%,说明磁盘 I/O 等待严重,这多是大量的磁盘随机访问或直接的磁盘访问(没有使用系统缓存)形成的,也可能磁盘自己存在瓶颈,能够结合 iostat 或 dstat 的输出加以验证,如 %wa(iowait) 升高同时观察到磁盘的读请求很大,说明多是磁盘读致使的问题。

此外,耗时较长的网络请求(即网络 I/O)也会致使 CPU 平均负载升高,如 MySQL 慢查询、使用 RPC 接口获取接口数据等。这种状况的排查通常须要结合应用自己的上下游依赖关系以及中间件埋点的 trace 日志,进行综合分析。

2.3 CPU 上下文切换次数变高

先用 vmstat 查看系统的上下文切换次数,而后经过 pidstat 观察进程的自愿上下文切换(cswch)和非自愿上下文切换(nvcswch)状况。自愿上下文切换,是由于应用内部线程状态发生转换所致,譬如调用 sleep()、join()、wait()等方法,或使用了 Lock 或 synchronized 锁结构;非自愿上下文切换,是由于线程因为被分配的时间片用完或因为执行优先级被调度器调度所致。

若是自愿上下文切换次数较高,意味着 CPU 存在资源获取等待,好比说,I/O、内存等系统资源不足等。若是是非自愿上下文切换次数较高,可能的缘由是应用内线程数过多,致使 CPU 时间片竞争激烈,频频被系统强制调度,此时能够结合 jstack 统计的线程数和线程状态分布加以佐证。

一、 内存相关

前面提到,内存分为系统内存和进程内存(含 Java 应用进程),通常咱们遇到的内存问题,绝大多数都会落在进程内存上,系统资源形成的瓶颈占比较小。对于 Java 进程,它自带的内存管理自动化地解决了两个问题:如何给对象分配内存以及如何回收分配给对象的内存,其核心是垃圾回收机制。

垃圾回收虽然能够有效地防止内存泄露、保证内存的有效使用,但也并非万能的,不合理的参数配置和代码逻辑,依然会带来一系列的内存问题。此外,早期的垃圾回收器,在功能性和回收效率上也不是很好,过多的 GC 参数设置很是依赖开发人员的调优经验。好比,对于最大堆内存的不恰当设置,可能会引起堆溢出或者堆震荡等一系列问题。

下面看看几个常见的内存问题分析思路。

关键字:设计模式 缓存 JavaScript 网络协议 前端开发 Java API 调度 Perl 容器

相关文章
相关标签/搜索