JVM调优—GC调优
1.堆内存分区
在具体介绍GC调优前,先复习下JVM内存结构中的堆
堆是JVM内存区域中所占空间最大的内存区域,是存放对象的区域,
堆分区如下
-
新生代
- eden区
- s0区
- s1区
-
老年代
其中新生代中的分区比例为:Eden区:s0区:s1区 = 8:1:1
为什么要进行堆分区?
主要是为了提高垃圾收集和对象内存分配的效率
对象内存分配:
1.对象优先在Eden区分配内存,也就是说,新生代存放在大部分的java对象,一个对象通过new实例化,会优先在Eden区进行内存分配,因此,Eden区是垃圾回收重点照顾的区域
2.大对象直接进入老年代,大对象是指需要大量连续内存空间的对象
3.长期存活的对象也将进入老年代,没精力一个GC,对象年龄就增加一岁,默认当对象年龄到达15岁时进入老年代
2.垃圾回收算法
标记-清除算法
思路
标记出所有需要收集的对象,标记完成后统一回收对象
缺陷
1.效率问题,标记和清除的效率不高
2.空间问题,会产生大量的碎片空间
复制算法
思路
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
缺陷
1.存在空间问题
标记-整理算法
思路
与标记-清除算法相似,但是,他会让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存空间
分代收集算法
思路
老年代采用标记-清除或者标记-整理算法(老年代每次垃圾收集对象存活率高,没有额外的空间对他进行分配担保
新生代采用复制算法 (新生代每次垃圾收集都有大批的对象死亡,只有少量的存活)
3.垃圾收集器总结
Serial收集器
串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)
Serial Old收集器
Serial收集器的老年代版本
ParNew收集器
ParNew收集器 ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-整理
Parallel Scavenge收集器
Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-整理
Parallel Old收集器
Parallel Scavenge收集器的老年代版本
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:
-
初始标记(CMS initial mark)
-
并发标记(CMS concurrent mark)
-
重新标记(CMS remark)
-
并发清除(CMS concurrent sweep)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew)
优点: 并发收集、低停顿
缺点: 产生大量空间碎片、并发阶段会降低吞吐量
G1收集器
G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:
空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿。
关于GC收集器和GC算法的解释可以参考
4.GC策略
Minor GC和Full GC的区别?
-
Minor GC:新生代GC,是发生在新生代的垃圾收集,Minor GC非常频繁,回收速度较快
-
Full GC:老年代GC,是指发生在老年代的垃圾收集,出现Full GC一般伴随一次Minor GC,并且Full GC一般比Minor GC慢10倍以上
GC策略
- 调整内存区域比例,主要是针对堆
- 选择合适的垃圾收集算法
- 选择合适的垃圾收集器
5.JVM参数调优
收集器的JVM参数
//************* Serial收集器 ********************/
-XX:+UseSerialGC Serial收集器
//************* ParNew收集器 ********************/
-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制回收线程数量
//************* Parallel Scavenge收集器 ********************/
-XX:+UseParallelGC 使用Parallel Scavenge收集器
-XX:MaxGCPauseMillis 最大垃圾收集停顿时间
-XX:GCTimeRatio 控制吞吐量大小
-XX:survivoRatio 设置Eden区和s1,s0区的比例默认是8:1
-XX:PretenureSizeThreshould 设置晋升老年代对象的大小
-XX:+UseAdaptiveSizePolicy 是否自动分配老年代大小和新生代中的Eden和s0的比例
//************* Parallel Old收集器 ********************/
-XX:+UseParallelOldGC 使用Parallel Old收集器
//************* CMS收集器 ********************/
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
//************* G1收集器 ********************/
-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC 开启
-XX:MaxGCPauseMillis 暂停时间目标
-XX:GCPauseIntervalMillis 暂停间隔目标;
-XX:+G1YoungGenSize 年轻代大小
-XX:SurvivorRatio=6 幸存区比例
详细可参考这篇博客