Java性能调优分享

By | 2020年9月9日

Java性能调优分享

这次分享还是之前在公司对项目进行性能调优时,前期的调研,以及积累的一些经验,之前公司产品面对的用户并发量较小,所以测试通过后直接上线,不需要根据其他维度来进行分析优化,后来领导对我负责的系统提出了并发的要求,想知道系统能承受的并发量是多少,时延会达到多长,根据这些需求,进行一系列压测。

1.为什么要做性能优化

  1. #### 系统角度

一款线上产品如果没有经过性能测试,那它就好比是一颗定时炸弹,你不知道它什么时候会出现问题,你也不清楚它能承受的极限在哪儿。有些性能问题是时间累积慢慢产生的,到了一定时间自然就爆炸了;而更多的性能问题是由访问量的波动导致的,等到用户量上来就会奔溃。
所有的系统在开发完之后,多多少少都会有性能问题,我们首先要做的就是想办法把问题暴露出来,例如进行压力测试、模拟可能的操作场景等等,再通过性能调优去解决这些问题。

  1. #### 个人角度

对技术能力的提升,强化思维方式
调优的对象不是单一的应用服务,性能可能与操作系统、网络、数据库等组件相关,所以我们需要储备操作系统、网络协议以及数据库等基础知识。具体的性能问题往往还与传输、计算、存储数据等相关,那我们还需要储备数据结构、算法以及数学等基础知识。

  1. 从更高的角度思考和总结

    我们在使用一项技术时,从来不问自己:为什么这项技术可以提升系统性能?对比其他技术它好在哪儿?实现的原理又是什么呢?只有深入源码,通过分析来学习、总结一项技术的实现原理和优缺点,这样我们就能更客观地去学习一项技术,才能在遇到性能问题时,做到触类旁通。

2. 性能体现在哪里

哪些计算机资源会成为系统的性能瓶颈

  1. CPU
    当CPU占用率太高时,需要去分析代码递归导致的无限循环,正则表达式引起的回溯,JVM 频繁的 FULL GC,以及多线程造成的大量上下文切换等,这些都有可能导致 CPU 资源繁忙。
  2. 内存
    Java一般通过 JVM 对内存进行分配管理,主要使用JVM中的堆来存储创建的对象。当内存空间被占满,对象无法回收时,就会导致内存溢出、内存泄露等问题
  3. 磁盘 I/O
    磁盘读写性能影响
  4. 网络
    带宽过低的话,对于传输数据比较大,或者是并发量比较大的系统,网络就很容易成为性能瓶颈。
  5. 数据库
    数据库的操作往往是涉及到磁盘 I/O 的读写。大量的数据库读写操作,会导致磁盘 I/O 性能瓶颈,进而导致数据库操作的延迟性,由于Gateway不涉及数据库的操作,本次不作为分析重点
  6. 锁竞争
    在并发编程中,经常需要多个线程共享读写操作同一个资源,这个时候为了保持数据的原子性,就会进行加锁操作。锁的使用可能会带来上下文切换,从而给系统带来性能开销。
  7. 异常处理
    Java 应用中,抛出异常需要构建异常栈,对栈进行快照,以便对异常进行捕获和处理,这个过程非常消耗系统性能。如果在高并发的情况下引发异常,持续地进行异常处理,那么系统的性能就会明显地受到影响。

3. 我们常说的用来衡量系统性能的指标到底是指什么,

  1. 响应时间
    响应时间是衡量系统性能的重要指标之一,响应时间越短,性能越好
    数据库响应时间
    服务端响应时间
    网络响应时间
  2. 吞吐量
    TPS(每秒事务处理量) 体现了接口的性能,TPS 越大,性能越好
    磁盘吞吐量
    网络吞吐量
  3. 资源占用率
    CPU 占用率、内存使用率、磁盘 I/O、网络 I/O 来表示资源使用率

4. 遇到的坑

  1. 压测工具的选择

  2. 服务器的选择

  3. 代码逻辑的问题

5. 不好测试

  1. 测试工具和服务器的选择

    性能测试结果不稳定,我们在做性能测试时发现,每次测试处理的数据集都是一样的,但测试结果却有差异。这是因为测试时,伴随着很多不稳定因素,比如机器其他进程的影响、网络波动以及每个阶段 JVM 垃圾回收的不同等等。
    我们可以通过多次测试,将测试结果求平均,或者统计一个曲线图,只要保证我们的平均值是在合理范围之内,而且波动不是很大,这种情况下,性能测试就是通过的

  2. 多 JVM 情况下的影响

    如果我们的服务器有多个 Java 应用服务,部署在不同的 Tomcat 下,这就意味着我们的服务器会有多个 JVM。任意一个 JVM 都拥有整个系统的资源使用权。如果一台机器上只部署单独的一个 JVM,在做性能测试时,测试结果很好,或者你调优的效果很好,但在一台机器多个 JVM 的情况下就不一定了。所以我们应该尽量避免线上环境中一台机器部署多个 JVM 的情况。

6.不好分析

吞吐量和延时是共同出现的,往往压测的时候没有标准可言,我们不知道性能的峰值是多少,这次Gateway因为使用的官方框架,所以有了可以参考的标准。

  1. 不知道具体哪里出现问题,只能一步一步打印耗时
  2. 不好调优
  3. 优化代码
  4. 优化设计
  5. 优化算法
  6. 优化参数
  7. 保底做法:限流

7.Linux命令的使用

top实时显示正在执行进程的 CPU 使用率、内存使用率以及系统负载等信息
mpstat -P ALL 查看多核CPU分核性能
vmstat 是一款指定采样周期和次数的功能性监测工具,我们可以使用它监控进程上下文切换的情况。
pidstat 命令可以监测到具体线程的上下文切换,监测线程的性能。

8.优化的方向

8.1 Java编程

  1. 从线程安全,数据变更的角度去选择String,StringBuffer,StringBuilder,重复性数据可使用String.intern 节省内存空间,慎用Split(),防止正则表达式带来的回溯问题。
  2. 处理大数据的集合时,尽量使用 Stream 的迭代方式进行处理。
  3. 使用 HashMap 时,可以结合自己的场景来设置初始容量和加载因子两个参数
  4. try-catch 代码段会产生额外的性能开销,会影响 JVM 对代码进行优化,建议仅捕获有必要的代码段
  5. Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作,尽量直接抛出异常

8.2 多线程性能调优

  1. 减少锁的持有时间
  2. 降低锁的粒度(读写锁分离,缩小锁住的代码段)
  3. volatile关键字可以保障可见性及有序性,不会导致上下文切换,开销比较小。
  4. 合理地设置线程池大小,避免创建过多线程
  5. 减少 Java 虚拟机的垃圾回收
  6. 并发容器的使用,如Hashtable和ConcurrentHashMap,ArrayList和CopyOnWriteArrayList

8.3 JVM性能监测及调优

GC 调优策略

  1. 调整堆内存降低 Full GC 的频率
  2. 调整年轻代降低 Minor GC 频率
  3. 选择合适的 GC 回收器

JVM命令
jstack 命令查看线程堆栈的运行情况,导出 Java 应用程序中的线程堆栈信息,排查一些死锁的异常。
jstat 可以监测 Java 应用程序的实时运行情况,包括堆内存信息以及垃圾回收信息。
jmap 查看堆内存初始化配置信息以及堆内存的使用情况,输出堆内存中的对象信息,包括产生了哪些对象,对象数量多少等。

8.4 设计模式调优

8.5 数据库性能调优(只以Redis为例,缺失了传统数据库的分析)

9.Java命令的参数

20200622105620377

发表评论

电子邮件地址不会被公开。 必填项已用*标注