目的
- 了解JAVA程序性能调优的过程、原理以及常见方法;在这方面做到“知道自己不知道”;
- 不用了解太多细节,这些细节的学习需要结合大量实践,只能有机会再深入。
TODO/疑问
- 为什么优化GC时,要按照“确定内存需求" -> "延迟调优" -> "吞吐量调优"的顺序? 是因为这些指标之间存在该顺序表明的依赖关系?
- 如何定义性能需求?
- 如何使用性能分析工具
- 不同类型(WEB, 后台SERVICE)应用的调优有特定的技术和思路,需要进一步深入学习
- GC算法水太深,暂时没有想到要在什么情况下需要了解GC的实现细节
基本概念
在开始介绍性能调优,我们需要了解一些相关的基本概念和术语
- 什么是性能指标?
一般包括:吞吐量、延迟、并发、资源占用等方面。这些指标构成一个整体,不可分割。- 性能监测(Profiling)
性能监控是指通过工具收集应用/系统运行时的相关数据(profile),为后续的性能分析提供输入。
性能监测参数
在性能监测过程中,我们经常要收集以下一些OS层面的参数,这些参数反应了应用的资源占用情况,以及应用的主要活动内容。
- 核心态CPU调用时间
该指标意味着应用在分配的CPU周期内执行了系统层面的操作,往往是I/O。理想情况下,该指标应该为0。
- CPU等待队列长度
该指标反映了系统CPU的繁忙程度。一般该长度应该小于系统的CPU核的个数,如果该长度长时间超过CPU个数,说明系统负担可能过重。
- 让步式上下文切换
让步式上下文切换表示程序内部存在多线程之间对锁的竞争。
调优过程
- 定义应用性能需求;
- 开发;
- 性能测试;
- 如果性能未达预期,则开始对应用进行性能监控(Profiling);
- 分析性能监控数据,对应用进行调优或者修改,回到2。
JAVA应用性能调优基本方法
基本方法:
- 优化算法和数据结构;
- 减少锁竞争:使用atomic/volatile;或者优化算法,缩小锁引起的竞争。
- 减少内核态CPU的使用:通过缓存或者批量模式减少IO;使用异步I/O
以下是结合性能分析工具,比较具体的方法:
- 通过分析GC中代的个数,找到可能存在的内存泄漏。
- 通过分析核心态CPU的比例,找到I/O阻塞引起的性能下降
- 通过分析锁的竞争情况,改进应用,减少竞争范围和几率,提供吞吐量。
- 通过分析对象分配情况,避免或者减少由于collection重新分配空间和拷贝导致的延迟
JVM性能调优基本方法
所谓JVM性能调优是指,通过调整JVM的部署模式,选择JRE的类型,设置GC参数,达到缩短应用处理延迟,提高应用吞吐量的目的。
GC在JVM调优中有重要地位,JVM调优方法中,很重要的一点就是降低GC频率,尤其是FULL GC(理想情况是0),所以需要先简单了解一下GC。
GC
-
内存(heap)布局
- 新生代
存放新创建的对象。包括Eden区,Survior区。
- 老年代
存放经过若干次GC,仍然存活的对象。
- 永久代
存放VM的元数据和CLASS数据。
-
内存管理
所有新对象都在
Eden
区生成,如果Eden
没有足够空间,将触发Minor GC;
Minor GC执行过程:新生代中不可达的对象将被释放,包括:
Eden
区域和Survivor
区域;
Survivor
区域有两个,其中一个保存上次GC发生后生存的对象,另一个则为空;
当发生Minor GC时,仍然存活的对象被移动到当前为空的Survivor
区域,Eden
区域和另一个Survivor
区域被清空;
所有对象都有个标记该对象生存时间的属性:代数,表示该对象已经经历过的GC次数。代数初始为0,每次GC后,仍然存活的对象的代数增加1; 如果某些对象的生存时间较长,这些对象将被晋升(Promtion)到老年代,即移动到老年代保存。FULL GC执行过程:
如果Minor GC过程中,发现老年代的空间不足以容纳下次需要晋升对象,触发FULL GC; 如果永生代空间不足,也会触发FULL GC;
-
实现算法
-
GC对性能的影响
- 吞吐量
- 延迟
- 内存占用
调优步骤
-
选择JVM部署模式 是一个还是多个JVM(即集群)
-
选择JRE类型
- CLIENT vs SERVER;
- 32 bit vs 64 bit; (需要考虑64位指针导致CPU高速缓存命中下降的问题)
-
调优/确定应用内存使用符合预期
- 给应用提供预期的负荷,让应用经过若干次FULL GC(可以手工触发)后,让应用运行在稳定态;
- 如果上述过程中出现OM异常,修改JVM的
-XMS
和-XMX
参数调整堆大小; - HEAP大小调整到位后,通过打印GC细节,观察老年代的内存占用情况;
- 根据稳定后的老年代的内存占用数据,确定应用的堆大小、新生代大小(2-4倍)和老年代大小(1.5倍)
- 如果能够确定应用的内存占用数据,则可以避免由于重新分配、拷贝引起的CPU周期。
-
调优延迟
- 通过增加空间大小,可以降低GC频率,提供应用吞吐量;但可能导致延迟增加。
- 如果对延迟敏感,可以更换GC算法,使用并行程度更高的实现
-
调优吞吐量
- 关键点就是降低GC频率,理想情况下甚至可以做到FULL GC次数为0(可能LMAX/DISRUPTOR有这样的处理)