Java性能调优分享
Java性能调优分享
这次分享还是之前在公司对项目进行性能调优时,前期的调研,以及积累的一些经验,之前公司产品面对的用户并发量较小,所以测试通过后直接上线,不需要根据其他维度来进行分析优化,后来领导对我负责的系统提出了并发的要求,想知道系统能承受的并发量是多少,时延会达到多长,根据这些需求,进行一系列压测。
1.为什么要做性能优化
一款线上产品如果没有经过性能测试,那它就好比是一颗定时炸弹,你不知道它什么时候会出现问题,你也不清楚它能承受的极限在哪儿。有些性能问题是时间累积慢慢产生的,到了一定时间自然就爆炸了;而更多的性能问题是由访问量的波动导致的,等到用户量上来就会奔溃。
所有的系统在开发完之后,多多少少都会有性能问题,我们首先要做的就是想办法把问题暴露出来,例如进行压力测试、模拟可能的操作场景等等,再通过性能调优去解决这些问题。
对技术能力的提升,强化思维方式
调优的对象不是单一的应用服务,性能可能与操作系统、网络、数据库等组件相关,所以我们需要储备操作系统、网络协议以及数据库等基础知识。具体的性能问题往往还与传输、计算、存储数据等相关,那我们还需要储备数据结构、算法以及数学等基础知识。
我们在使用一项技术时,从来不问自己:为什么这项技术可以提升系统性能?对比其他技术它好在哪儿?实现的原理又是什么呢?只有深入源码,通过分析来学习、总结一项技术的实现原理和优缺点,这样我们就能更客观地去学习一项技术,才能在遇到性能问题时,做到触类旁通。
2. 性能体现在哪里
哪些计算机资源会成为系统的性能瓶颈
- CPU
当CPU占用率太高时,需要去分析代码递归导致的无限循环,正则表达式引起的回溯,JVM 频繁的 FULL GC,以及多线程造成的大量上下文切换等,这些都有可能导致 CPU 资源繁忙。 - 内存
Java一般通过 JVM 对内存进行分配管理,主要使用JVM中的堆来存储创建的对象。当内存空间被占满,对象无法回收时,就会导致内存溢出、内存泄露等问题 - 磁盘 I/O
磁盘读写性能影响 - 网络
带宽过低的话,对于传输数据比较大,或者是并发量比较大的系统,网络就很容易成为性能瓶颈。 - 数据库
数据库的操作往往是涉及到磁盘 I/O 的读写。大量的数据库读写操作,会导致磁盘 I/O 性能瓶颈,进而导致数据库操作的延迟性,由于Gateway不涉及数据库的操作,本次不作为分析重点 - 锁竞争
在并发编程中,经常需要多个线程共享读写操作同一个资源,这个时候为了保持数据的原子性,就会进行加锁操作。锁的使用可能会带来上下文切换,从而给系统带来性能开销。 - 异常处理
Java 应用中,抛出异常需要构建异常栈,对栈进行快照,以便对异常进行捕获和处理,这个过程非常消耗系统性能。如果在高并发的情况下引发异常,持续地进行异常处理,那么系统的性能就会明显地受到影响。
3. 我们常说的用来衡量系统性能的指标到底是指什么,
- 响应时间
响应时间是衡量系统性能的重要指标之一,响应时间越短,性能越好
数据库响应时间
服务端响应时间
网络响应时间 - 吞吐量
TPS(每秒事务处理量) 体现了接口的性能,TPS 越大,性能越好
磁盘吞吐量
网络吞吐量 - 资源占用率
CPU 占用率、内存使用率、磁盘 I/O、网络 I/O 来表示资源使用率
4. 遇到的坑
-
压测工具的选择
-
服务器的选择
-
代码逻辑的问题
5. 不好测试
-
测试工具和服务器的选择
性能测试结果不稳定,我们在做性能测试时发现,每次测试处理的数据集都是一样的,但测试结果却有差异。这是因为测试时,伴随着很多不稳定因素,比如机器其他进程的影响、网络波动以及每个阶段 JVM 垃圾回收的不同等等。
我们可以通过多次测试,将测试结果求平均,或者统计一个曲线图,只要保证我们的平均值是在合理范围之内,而且波动不是很大,这种情况下,性能测试就是通过的 -
多 JVM 情况下的影响
如果我们的服务器有多个 Java 应用服务,部署在不同的 Tomcat 下,这就意味着我们的服务器会有多个 JVM。任意一个 JVM 都拥有整个系统的资源使用权。如果一台机器上只部署单独的一个 JVM,在做性能测试时,测试结果很好,或者你调优的效果很好,但在一台机器多个 JVM 的情况下就不一定了。所以我们应该尽量避免线上环境中一台机器部署多个 JVM 的情况。
6.不好分析
吞吐量和延时是共同出现的,往往压测的时候没有标准可言,我们不知道性能的峰值是多少,这次Gateway因为使用的官方框架,所以有了可以参考的标准。
- 不知道具体哪里出现问题,只能一步一步打印耗时
- 不好调优
- 优化代码
- 优化设计
- 优化算法
- 优化参数
- 保底做法:限流
7.Linux命令的使用
top实时显示正在执行进程的 CPU 使用率、内存使用率以及系统负载等信息
mpstat -P ALL 查看多核CPU分核性能
vmstat 是一款指定采样周期和次数的功能性监测工具,我们可以使用它监控进程上下文切换的情况。
pidstat 命令可以监测到具体线程的上下文切换,监测线程的性能。
8.优化的方向
8.1 Java编程
- 从线程安全,数据变更的角度去选择String,StringBuffer,StringBuilder,重复性数据可使用String.intern 节省内存空间,慎用Split(),防止正则表达式带来的回溯问题。
- 处理大数据的集合时,尽量使用 Stream 的迭代方式进行处理。
- 使用 HashMap 时,可以结合自己的场景来设置初始容量和加载因子两个参数
- try-catch 代码段会产生额外的性能开销,会影响 JVM 对代码进行优化,建议仅捕获有必要的代码段
- Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作,尽量直接抛出异常
8.2 多线程性能调优
- 减少锁的持有时间
- 降低锁的粒度(读写锁分离,缩小锁住的代码段)
- volatile关键字可以保障可见性及有序性,不会导致上下文切换,开销比较小。
- 合理地设置线程池大小,避免创建过多线程
- 减少 Java 虚拟机的垃圾回收
- 并发容器的使用,如Hashtable和ConcurrentHashMap,ArrayList和CopyOnWriteArrayList
8.3 JVM性能监测及调优
GC 调优策略
- 调整堆内存降低 Full GC 的频率
- 调整年轻代降低 Minor GC 频率
- 选择合适的 GC 回收器
JVM命令
jstack 命令查看线程堆栈的运行情况,导出 Java 应用程序中的线程堆栈信息,排查一些死锁的异常。
jstat 可以监测 Java 应用程序的实时运行情况,包括堆内存信息以及垃圾回收信息。
jmap 查看堆内存初始化配置信息以及堆内存的使用情况,输出堆内存中的对象信息,包括产生了哪些对象,对象数量多少等。