【原创】HotSpot JVM性能优化(一)
本文只讨论JVM本身的性能优化,不涉及通过系统架构角度调优整体性能,原因是想重点讨论JVM本身。架构级的性能优化后续会总结归纳单独讨论。
首先,需要确定调优目标,即最求系统的响应时效性或者是CPU计算吞吐量。之所以这么说的原因是,固定CPU的情况下,CPU的计算能力几乎是一定的(排除超频)。追求响应时效性,要让CPU的计算要尽可能在最短时间内满足应用程序本身的线程调用,而不是让应用程序线程处于等待状态;追求CPU吞吐量,是要在一个段内CPU的计算尽可能多的用于应用程序本身的线程,也可以说成是(CPU有效技术/CPU计算量)×时间的值。
通常,与用户交互较频繁的系统更加关注系统的响应时效性,与用户交互较少甚至几乎无交互的系统更关注CPU计算吞吐量。
影响一个应用系统性能因素基本上有几点:CPU、内存、硬盘、网络、其他外设。此处排除代码逻辑导致的系统性能问题(假设认为应用程序本身的逻辑都是业务需求且合理的)。几点因素中能涉及到JVM的性能优化基本只有内存一个。
由于Java语言不再需要开发人员管理内存,内存回收工作全部由JVM的垃圾回收器负责了。因此,花在内存回收上的CPU计算就也就成了影响JVM性能的一个很重要的元凶。假想一下,如果你的CPU大部分时间都在忙着替你整理内存而顾不上给你真正需要做的事情服务,系统的高性能从何谈起呢?
要优化内存回收,就要先看下JVM是怎么管理使用内存的,《Java虚拟机(Java SE 7版)》规定,Java虚拟机管理的内存包括如下几部分:
1、堆(Heap);
2、虚拟机栈(VM Stack);
3、本地方法栈(Native Method Stack);
4、方法区(Method Area);
5、程序计数器(Program Counter Register);
注:Sun HotSpot直接将2、3合二为一。
而为了内存回收方便,又把这几个区域分为:新生代(Young)、老年代(Tenured)、方法区(永久代Perm)。虚拟机的垃圾回收器是按代分类的,重点在新生代和老年代。下表是各回收器的所处分代、自身特性及推荐的使用场景等信息
回收器名称 | 所处分代 | 并行性 | 使用收集算法 | 推荐使用场景 |
Serial | 新生代 | 串行 | 复制算法 | 单核CPU系统 |
Serial OLD | 老年代 | 串行 | 标记整理 | 单核CPU系统、CMS回收器的后备军 |
ParNew | 新生代 | 并行 | 复制算法 | 多核CPU系统,-server模式下 默认新生代收集器 |
Parallel Scavenge | 新生代 | 并行 | 复制算法 | 注重CPU吞吐量的系统 |
Parallel Old | 老年代 | 并行 | 标记整理 | 注重CPU吞吐量的系统 |
Concurrent Mark Sweep(CMS) | 老年代 | 并行 | 标记清除 | 多核系统,注重响应时效性 |
Garbage-First(G1) | 全部 | 并行 | 标记整理 | 垃圾回收器的最前沿,性能有待考验 |
新老年代回收器的组合方式如下图:
可以通过JVM参数来制定使用何种组合,使用命令:java -XX:+PrintFlagsFinal查看除了Diagnostic和Expermental外的所有非稳定参数的名称及默认值
这里仅给出如何制定垃圾回收器组合的参数
参数 | 默认值 | 回收器组合 |
-XX:+UseSerialGC | -Client默认开启 | Serial+Serial Old |
-XX:+UseParallelOldGC | 默认关闭 | Parallel Scavenge+Parallel Old |
-XX:+UseParallelGC | -Server默认开启 | Parallel Scavenge+Serial Old |
-XX:+UseParNewGC | 默认关闭 | ParNew+Serial Old |
-XX:+UseConcMarkSweepGC | 默认关闭 | ParNew+CMS+SerialOld |
-XX:+UseG1GC | 默认关闭 | G1 |
从上表可见,如果我们追求响应时效性,可以考虑将内存回收器组合调整为ParNew+CMS+SerialOld模式尝试一下;如果追求CPU吞吐量,可以调整为Parallel Scanvenge+Parallel Old模式。当然,这只是在选择内存回收组合层面。
还要根据根据具体的应用程序对内存的使用量,新老生代内存的使用量,方法区使用量等因素,相应的调整内存的堆大小,方法区最大值等。最终达到减少内存回收次数及总回收时间的目的。
本文旨在抛砖引玉,欢迎大家提出各自的经验看法讨论