调优基本概念
在调整性能时,JVM有三个组件:
1、堆大小调整
2、垃圾收集器调整
3、JIT编辑器
大多数调优选项都与调整堆大小和对应情况的垃圾收集器有关,JIT编辑器对性能有很大的影响,但很少需要使用较新版本的JVM进行调优。
通常,在调优Java应用程序时重点是以下两个主要目标之一:
1、响应性:应用程序或系统对请求的数据进行响应的速度,对于专注于响应性的应用程序,长的暂停时间是不可以接受的,重点是在短时间内做出的回应。
2、吞吐量:侧重于在特定时间内最大化应用程序的工作量,对于专注于吞吐量的应用程序,高暂停时间是可以接受的。由于高吞吐量应用程序在较长时间内专注于基准测试,因此不需要考虑快速响应时间。
GC调优思路
1、分析场景
例如:启动速度慢;偶尔出现响应慢于平均水平或者出现卡顿。
2、确定目标
内存占用、低延时、吞吐量。
3、收集日志
通过参数配置手机GC日志;通过JDK工具查看GC状态。
4、分析日志
使用工具辅助分析日志,查看GC次数,GC时间
5、调整参数
切换垃圾收集器或者调整垃圾收集器参数
GC收集器介绍
串行收集器
串行收集器组合 Serial + Serial Old
开启选项:-XX:+SerialGC
串行收集器是最基本、发展时间最长、久经考验的垃圾收集器,也是client模式下的默认收集器配置。
串行收集器采用单线程stop-the-world的方式进行收集。当内存不足时,串行GC设置停顿标识,待所有线程都进入安全点(Safepoint)时,应用线程暂停,串行GC开始工作,采用单线程方式回收空间并整理内存。单线程也意味着复杂度更低、占用内存更少,但同时也意味着不能有效利用多核优势。事实上,串行收集器特别适合堆内存不高、单核甚至双核CPU的场合。
并行收集器
并行收集器组合 Parallel Scavenge + Parallel Old
开启选项:-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活)
并行收集器是以关注吞吐量为目标的垃圾收集器,也是server模式下的默认收集器配置,对吞吐量的关注主要体现在年轻代Parallel Scavenge收集器上。
并行收集器与串行收集器工作模式相似,都是stop-the-world方式,只是暂停时并行地进行垃圾收集。年轻代采用复制算法,老年代采用标记-整理,在回收的同时还会对内存进行压缩。关注吞吐量主要指年轻代的Parallel Scavenge收集器,通过两个目标参数-XX:MaxGCPauseMills和-XX:GCTimeRatio,调整新生代空间大小,来降低GC触发的频率。并行收集器适合对吞吐量要求远远高于延迟要求的场景,并且在满足最差延时的情况下,并行收集器将提供最佳的吞吐量。
CMS标记清除收集器
并发标记清除收集器组合 ParNew + CMS + Serial Old
开启选项:-XX:+UseConcMarkSweepGC
并发标记清除(CMS)是以关注延迟为目标、十分优秀的垃圾回收算法,开启后,年轻代使用STW式的并行收集,老年代回收采用CMS进行垃圾回收,对延迟的关注也主要体现在老年代CMS上。
年轻代ParNew与并行收集器类似,而老年代CMS每个收集周期都要经历:初始标记、并发标记、重新标记、并发清除。其中,初始标记以STW的方式标记所有的根对象;并发标记则同应用线程一起并行,标记出根对象的可达路径;在进行垃圾回收前,CMS再以一个STW进行重新标记,标记那些由mutator线程(指引起数据变化的线程,即应用线程)修改而可能错过的可达对象;最后得到的不可达对象将在并发清除阶段进行回收。值得注意的是,初始标记和重新标记都已优化为多线程执行。CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
但是CMS并不完美,它有以下缺点:
由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间;
标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。
G1收集器
Garbage First (G1)
开启选项:-XX:+UseG1GC
之前介绍的几组垃圾收集器组合,都有几个共同点:
年轻代、老年代是独立且连续的内存块;
年轻代收集使用单eden、双survivor进行复制算法;
老年代收集必须扫描整个老年代区域;
都是以尽可能少而块地执行GC为设计原则。
G1垃圾收集器也是以关注延迟为目标、服务器端应用的垃圾收集器,被HotSpot团队寄予取代CMS的使命,也是一个非常具有调优潜力的垃圾收集器。虽然G1也有类似CMS的收集动作:初始标记、并发标记、重新标记、清除、转移回收,并且也以一个串行收集器做担保机制,但单纯地以类似前三种的过程描述显得并不是很妥当。事实上,G1收集与以上三组收集器有很大不同:
G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
调优参数
在调优测试前,一些常用的JVM参数需要了解:
参数 | 参数功能 |
---|---|
-XX:+AlwaysPreTouc | jvm启动时分配内存,非使用时再分配 |
-XX: ErrorFile=filename | 崩溃日志 |
-XX:TraceClassLoading | 跟踪类加载信息 |
-XX:+PrintClassHistogram | 按下Ctrl+Break后,打印类的信息 |
-Xmx -Xms | 最大堆和最小堆 |
-xx:permSize、-xx:metaspaceSize | 永久代/元数据空间 |
-XX:+HeapDumpOnOutOfMemoryError | OOM时导出堆到文件 |
-XX:+HeapDumpPath | OOM时堆导出的路径 |
-XX:OnOutOfMemoryError | 在OOM时,执行一个脚本 |
java -XX:+PrintFlagsFinal -Version | 打印所有的-XX参数和默认值 |
通用GC参数 | 参数功能 |
---|---|
-XX:ParallellGCThreads | 并行GC线程数量 |
-XX:ConcGCThreads | 并发GC线程数量 |
-XX:MaxGCPauseMillis | 最大停顿时间,单位毫秒,GC尽力保证回收时间不超过设定值 |
-XX:GCTimeRatio | 0-100的取值范围,垃圾收集时间占总时间的比,默认99,即最大允许1%的时间做GC |
-XX:SurvivorRatio | 设置Eden区大小和Survivor区大小的比例,8表示两个Survivor:Eden=2:8,即一个Survivor占年轻代的1/10 |
-XX:NewRatio | 新生代和老年代的比,4表示新生代:老年代=1:4,即年轻代占堆的1/5 |
-verbose:gc、-XX:+printGC | 打印GC的简要信息 |
-XX:+PrintGCDetails | 打印GC的详细信息 |
-XX:+PrintGCTimeStamps | 打印GC发生的时间戳 |
-Xloggc:log/gc.log | 指定GC log的位置,以文件输出 |
-XX:+PrintHeapAtGC | 每次一次GC后,都打印堆信息 |
后面将会调节几组垃圾收集器对应的参数来观察对吞吐量和响应时长产生的变化。