JVM性能调优以及实操

1. 性能调优

性能调优包含多个层次,比如:架构调优、代码调优、JVM调优、数据库调优、操作系统调优等。

架构调优和代码调优是JVM调优的基础,其中架构调优是对系统影响最大的。

性能调优基本按照一下步骤进行:明确优化目标、发现性能瓶颈、性能调优、通过监控以及数据统计工具获得数据、确实是否达到目标。

1.1 何时进行JVM调优

遇到一下情况,需要考虑进行JVM调优了:

  • Heap内存(老年代)持续上涨到设置的最大内存值

  • Full GC次数频繁

  • GC停顿时间过长(超过1秒)

-应用出现OutOfMemory等内存异常

  • 应用中有使用本地缓存且占用大量内存空间

  • 系统吞吐量与响应性能不高或下降

1.2 JVM调优的基本原则

JVM调优是一个手段,但并不一定所有问题都可以通过JVM进行调优解决,因催,在进行JVM调优时,我们要遵循一些原:

  • 大多数的Java应用不需要进行JVM优化

  • 大多数导致GC问题的原因是代码层面的额问题导致的

  • 上线之前,应先考虑将机器的JVM参数设置到最优

  • 减少创建对象的数量(代码层面)

  • 减少使用全局变量和大对象(代码层面)

  • 优先架构调优和代码调优,JVM优化是不得已的手段(代码、架构层面)

  • 分析GC情况优化代码比优化JVM参数更好(代码层面)

通过以上原则,我们发现,其实最有效的优化手段是架构和代码层面的优化,而JVM优化则是最后不得已的手段,也可以是对服务器配置的最后一次”压榨”

1.3 JVM调优目标

调优的最终目的都是为了令应用程序使用最小的硬件消耗来承载更大的吞吐。JVM调优主要是针对垃圾收集器的收集性能优化,令运行在虚拟机上的应用能够使用更少的内存以及延迟获取更大的吞吐量

  • 延迟:GC低停顿和GC低频率

  • 低内存占用

  • 高吞吐量

其中,任何一个属性性能的提高,几乎都是牺牲其他属性性能为代价的,不可兼得。

1.4 JVM调优量化目标

下面展示一些JVM调优的量化目标参考实例:

  • Heap内存使用率 <= 70%

  • Old Generation 内存使用率 <= 70%

  • avgpause <= 1秒

  • Full GC次数0 或者 avg pause interval >= 24小时

注意:不同应用的JVM调优量化目标是不一样的。

1.5 JVM参数

JVM调哟最重要的工具就是JVM参数了。先来了解一下JVM参数相关内容。

-XX 参数被称为不稳定参数,此类参数的设置很容易引起JVM性能上的差异,使JVM存在极大的不稳定性。如果此类参数设置合理将大大提高JVM的性能及稳定性。

不稳定参数语法规则包含一下内容:

  • 布尔类型参数值:

    • -XX:+:’+’ 表示启用该选项
    • -XX:-:’-‘ 表示关闭该选项
  • 数值类型参数值:

    • -XX:= 给选项设置一个数值类型值,可跟随单位,例如:m或M表示兆字节, k或K表示千字节,g或G表示千兆字节。32K和32768是相同大小的
  • 字符串类型参数值:

    • -XX:=给选项设置一个字符串类型值,通常用于指定一个文件、路径或一系列命令列表。例如:-XX:HeapDumpPath=./dump.core

1.6 JVM参数解析及调优

比如一下参数示例:

1
-Xmx4g –Xms4g –Xmn1200m –Xss512k -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:PermSize=100m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15

上面为Java7及以前版本的示例,在Java8中永久代的参数-XX:PermSize-XX:MaxPermSize已经失效。

参数解析:

  • -Xmx4g:堆内存最大值为4GB

  • -Xms4g:初始化堆内存大小为4GB

  • -Xmn1200m:设置年轻代大小为1200MB,增大年轻代后,将会减少老年代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的 3/8.

  • -Xss512k:设置每个线程堆栈大小,JDK5.0以后每个线程堆栈大小为1MB,以前每个线程堆栈大小为256K,应根据应用线程所需内存大小调整。在相同物理内存下,减少这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成。经验值在3000-5000左右

  • -XX:NewRation=4:设置年轻代(包括Eden和两个Survivor区)与老年代的比值(除去持久代)。设置为4,则年轻代 / 老年代的比值为1:4, 年轻代占整个堆栈的 1/5

  • -XX:SurvivorRatio=8:设置年轻代中Eden区与Survivor区的大小比值,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代1/10;SurvivorRation值就是设置Eden区的比例占多少, s0/ s1相同

  • -XX:PermSize=100m:初始化永久代大小为100MB

  • -XX:MaxPermSize=256m:设置持久代大小为256MB

  • -XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的化,则年轻代对象不经过Survivor区,直接进入老年代。对于老年代比较多的应用,可以提高效率。如果将此值设置为一个较大的值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率

新生代、老生代、永久代参数,如果不惊醒设定,虚拟机会自动选择何时的值,同时也会基于系统的开销自动调整。

可调优参数:

  • Xms:初始化堆内存的大小,默认为物理内存的1/64(小于1GB)

  • Xmx:堆内存最大值,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于 70%时,JVM会减少堆直到 -Xms的最小限制

  • Xmn:新生代大小,包活Eden区和2个Survivor区

  • -XX:SurvivorRation=1:Eden区与一个Survivor区比值为1:1

  • -XX:MaxDirectMemorySize=1GB:直接内存。报java.lang.OutOfMemoryError:Direct buffer memory异常可以上调这个值。

  • -XX:+DisableExplicitGC:禁止运行期显式的调用System.gc()来触发full GC;注意:Java RMI的定时GC触发机制可以通过配置-Dsun.rmi.dgc.server.glInterval=86400来控制触发时间
  • -XX:CMSInitiatingOccupancyFraction=60:老年代内存回收阈值,默认值68

  • -XX:ConcGCThreads=4:CMS垃圾回收器并行线程数,推荐值为CPU核心数

  • -XXParallelGCThreads=8:新生代并行收集器的线程数

  • -XX:MasxTenuringThreshold=10:设置来及最大年龄,如果设置为0的化,则年轻代对象不经过Survivor区,直接进入老年代,杜宇老年代比较多的应用,可以提高效率,如果将此值设置比较大的话,则年轻代对象会在Survivor区进行多次复制,这样可以增的存活时间,增加在年轻代被回收的概率

  • -XX:CMSFullGCsBeforeCompaction=4:指定进行多少次full GC之后,进行tenured区内存空间压缩

  • -XX:CMSMaxAbortablePrecleanTime=500:当abortable-preclean预清理阶段执行达到这个时间时就会结束。

jvm内存模型

2. 内存优化示例

当JVM运行稳定之后,触发来FullGC我们一般会拿到如下信息:

以上gc日志中,在发生fullGC之时,整个应用的堆占用以及GC时间,为了更加精确需多次收集,计算平均值,或者时采集耗时最长的一次FullGC来进行估算。上图中,老年代占用93168k,以此定位老年代空间的活跃数据,则其他的堆空间分配,基于以下规则来进行。

  • java heap: 参数-Xms-Xmx,建议扩大至3-4被FullGC后的老年代空间占用

  • 永久代:-XX:PermSize-XX:MaxPermSize,建议扩大至1.2-1.5倍FullGC后的永久代空间占用

  • 新生代:-Xmn,建议扩大至1-1.5倍FullGC之后的老年代空间占用。

  • 老年代: 2-3倍FullGC后的老年代空间占用

基于以上规则,则对参数定义如下:

1
java -Xms373m -Xmx373m -Xmn140m -XX:PermSize=5m -XX:MaxPermSize=5m