前言
对Java虚拟机进行性能调优是一个非常宽泛的话题,在实践上也是非常棘手的过程。因为它需要一种系统的优化方法和清晰的优化期望。默认的JVM参数尝试在大多数情况下提供可接受的性能;但是,根据应用程序的行为和它所处的工作负载,默认值可能不会产生理想的结果。如果Java虚拟机没有按照预期运行就可能需要对应用程序进行基准测试并进行调优,以找到一组合适的 JVM参数。
大多数情况下谈论的“JVM调优”都是在说“Java GC调优”,Java官方手册上关于HotSpot虚拟机下的第一个主题就是HotSpot Virtual Machine Garbage Collection Tuning Guide。
本文记录的是从真实业务开发GC调优中总结的一个“基本步骤和流程“,结合着一些参考资料给出一个案例,以及个人对这块知识点的理解进行讲解。
1. 开始优化前的准备
- 深入理解需要优化系统的业务逻辑
- 深入理解Java虚拟机相关的概念(书籍、官方文档)
- 思考一下JVM调优的目的,真的需要调优吗?90%以上的问题都是业务代码导致的,把调优的精力放到业务逻辑的优化上是不是可以达到更好的效果?
有一些因素会影响你得到的最终调优参数:
- JDK的版本
- 服务器硬件
- 操作系统
- 系统的负载曲线
- 系统的业务类型,优化需要围绕的业务类型进行
- 系统的数据集
以上所有内容构成了调优GC性能的环境。调优参数越具体,解决方案就越不通用,它适用的环境也就越少。这意味着,如果任何变量发生变化(例如,更多用户被授予请求应用程序、应用程序升级、硬件升级的权限),那么所做的任何性能调优都可能需要重新评估。
另外必须理解通过显式调优,实际上可能会降低性能。重要的是要持续监控应用程序,并检查基于调优的假设是否仍然有效。如果经过仔细的调试后仍达不到目标,或许应该考虑GC调优之外的更改,例如更换更适合的硬件、操作系统调优和应用程序调优。
Default Arrangement of Generations 。 图不重要,看文本~
2.选择优化目标
JVM优化首先需要选择目标。在下一步为GC调优准备系统时围绕着这些目标设置值。可参考的目标选择方向:
- 延迟——JVM在执行垃圾收集时引起的Stop The World(STW)。有两个主要的指标,平均GC延迟和最大GC延迟。这个目标的动机通常与客户感知到的性能或响应能力有关。
- 吞吐量——JVM可用于执行应用程序的时间百分比。可用来执行应用程序的时间越多,可用来服务请求的处理时间就越多。需要注意的是,高吞吐量和低延迟并不一定相关——高吞吐量可能伴随着较长但不频繁的暂停时间。
- 内存成本——内存占用是JVM为执行应用程序所消耗的内存量。如果应用程序环境内存有限,将此项设为目标也可以起到降低成本的作用。
以上内容可以在Java官方手册的HotSpot调优部分找到,而一些经验性的优化原则如下:
- 优先考虑MinorGC。在大多数应用程序中,大多数垃圾都是由最近短暂的对象分配创建的,所以优先考虑年轻代的GC。年轻代对象生命周期越短,不会因为动态年龄判断和空间分配担保等因素导致存活时间不长的对象被分配到老年代;同时老年代对象生命周期长,JVM的整体垃圾收集就越高效,这将导致更高的吞吐量。
- 设定合适的堆区大小。给JVM的内存越多,收集频率就越低。此外,这还意味着可以适当地调整年轻代的大小,以更好地应对短期对象的创建速度,这就减少了向老年代分配的对象数量。
- 设定简单的目标。为了使事情变得更容易,把JVM调优的目标设定的简单一点,例如只选择其中两个性能目标进行调整,而牺牲另一个甚至只选择一个。通常情况下,这些目标是相互竞争的,例如,为了提高吞吐量,你给堆的内存越多,平均暂停时间就可能越长;反之,如果你给堆的内存越少,从而减少了平均暂停时间,暂停频率就可能增加,降低吞吐量。同样,对于堆的大小,如果所有分代区域的大小合适则可以提供更好的延迟和吞吐量,这通常是以牺牲JVM的占用率为代价的。
简而言之,调整GC是一种平衡的行为。通常无法仅仅通过GC的调整来实现所有的目标。
Typical Distribution for Lifetimes of Objects 。 图不重要,看文本~
3.业务逻辑分析
这里并不会把真实的业务系统拿出来进行分析,而是模拟类似的场景,大致的业务信息如下:
- 一个微服务系统(本例中是一个SpringBoot Demo,以下简称D服务)主要提供书籍实体信息的缓存和搜索业务
- 底层数据库为MySQL+Redis+Elasticsearch
- 业务场景为向其他微服务模块或者其他部分提供高速缓存及书籍信息检索
- 实际实现需求时采用MySQL+Redis+应用内存式多级缓存
从D服务的性质来说并不属于用户强交互类型的后端服务,主要面向的部门内的其他微服务和其他部门的后端服务,所以对于请求的响应速度(延迟)并不属于第一目标,所以可以把主要的调优目标设定为增加吞吐量或者降低内存占用。当然由于服务本身并不算臃肿,生产资源也不是很吃紧,所以也不考虑降低内存,则最后的目标为增加吞吐量。
从业务逻辑的角度分析主要是对书籍信息的增删改查,请求流量中90%以上是查,增删改则大多是服务内部对于多方数据的维护。
查询的类型分为多种,从对象的角度来说大多数是短期对象,例如:
- 通过AdvanceSearch(AS)模块生成的复杂ES查询语句对象(数据量小,频率中等,存活时间极短)
- 从多方数据源整合业务逻辑的数据响应对象(数据量中,频率极高,存活时间极短)
- 多级缓存体系中在内存中暂存的数据对象(数据量小,频率高,存活时间中等,因为是LRU缓存)
- 定期维护任务处理分发的数据对象(数据量大、频率低、存活时间极短)
可能会进入老年代的业务对象只有内存缓存中的对象,这一块结合业务量+过期时间常时保持在数百MB以内,这里的大小是多次压测中Dump堆内存+MAT分析得出的结果。
统合以上所有信息可以看出这是一个大部分对象存活时间都不会太长的应用,所以初步的想法是增大年轻代的大小,让大部分对象都在年轻代被回收,减少进入老年代的几率。
下面贴