Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lidaling/bc0b2d11b0d5c6e06d47b2cae9e40243 to your computer and use it in GitHub Desktop.
Save lidaling/bc0b2d11b0d5c6e06d47b2cae9e40243 to your computer and use it in GitHub Desktop.
<java性能优化权威指南>读后总结

目的

  • 了解JAVA程序性能调优的过程、原理以及常见方法;在这方面做到“知道自己不知道”;
  • 不用了解太多细节,这些细节的学习需要结合大量实践,只能有机会再深入。

TODO/疑问

  • 为什么优化GC时,要按照“确定内存需求" -> "延迟调优" -> "吞吐量调优"的顺序? 是因为这些指标之间存在该顺序表明的依赖关系?
  • 如何定义性能需求?
  • 如何使用性能分析工具
  • 不同类型(WEB, 后台SERVICE)应用的调优有特定的技术和思路,需要进一步深入学习
  • GC算法水太深,暂时没有想到要在什么情况下需要了解GC的实现细节

基本概念
在开始介绍性能调优,我们需要了解一些相关的基本概念和术语

  • 什么是性能指标?
    一般包括:吞吐量、延迟、并发、资源占用等方面。这些指标构成一个整体,不可分割。
  • 性能监测(Profiling)
    性能监控是指通过工具收集应用/系统运行时的相关数据(profile),为后续的性能分析提供输入。

性能监测参数
在性能监测过程中,我们经常要收集以下一些OS层面的参数,这些参数反应了应用的资源占用情况,以及应用的主要活动内容。

  • 核心态CPU调用时间
    该指标意味着应用在分配的CPU周期内执行了系统层面的操作,往往是I/O。理想情况下,该指标应该为0。
  • CPU等待队列长度
    该指标反映了系统CPU的繁忙程度。一般该长度应该小于系统的CPU核的个数,如果该长度长时间超过CPU个数,说明系统负担可能过重。
  • 让步式上下文切换
    让步式上下文切换表示程序内部存在多线程之间对锁的竞争。

调优过程

  1. 定义应用性能需求;
  2. 开发;
  3. 性能测试;
  4. 如果性能未达预期,则开始对应用进行性能监控(Profiling);
  5. 分析性能监控数据,对应用进行调优或者修改,回到2。

书上的原图

JAVA应用性能调优基本方法
基本方法:

  • 优化算法和数据结构;
  • 减少锁竞争:使用atomic/volatile;或者优化算法,缩小锁引起的竞争。
  • 减少内核态CPU的使用:通过缓存或者批量模式减少IO;使用异步I/O

以下是结合性能分析工具,比较具体的方法:

  • 通过分析GC中代的个数,找到可能存在的内存泄漏。
  • 通过分析核心态CPU的比例,找到I/O阻塞引起的性能下降
  • 通过分析锁的竞争情况,改进应用,减少竞争范围和几率,提供吞吐量。
  • 通过分析对象分配情况,避免或者减少由于collection重新分配空间和拷贝导致的延迟

书上的原图

JVM性能调优基本方法
所谓JVM性能调优是指,通过调整JVM的部署模式,选择JRE的类型,设置GC参数,达到缩短应用处理延迟,提高应用吞吐量的目的。 JVM

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有这样的处理)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment