大家应该熟悉JVM的内存模型了,因为本文讲JVM的调优所以有必要复习一下JVM的堆内存模型.
为什么JVM要采用这么复杂的内存管理,直接放一块不好吗?之所以会采用分代的方式,完全是为了方便快速高效GC而设计。
因为本文都是讲New Parallel 配合CMS GC的方式,所以不展开讨论G1,但是G1收集器是未来GC的方向,而且现在已经商业成熟了,感兴趣同学可以去详细了解
最基础的GC算法,首先标记,然后清除
特点:1.算法简单,2.效率低,2.会产生大量内存碎片
将内存分为两块,在GC时从一块复制到另一块
特点:1.算法简单且高效,2.会浪费二分之一的空间,3.在对象存活率低的时候高效,在对象存活率高的时候就变得低效
老年代的对象存活率较高,复制算法非常耗时.针对这种情况,标记整理在标记清除上进行优化,让存活对象移到一端,然后从边界位置直接清理.
串行: 单线程,只有一个GC线程执行任务
并行: 多线程,多个GC线程执行任务,不能与用户线程同时进行
并发: 能与用户线程同时进行的GC
顾名思义,单线程作战,所以比较低效,优点是GC使用线程少,所以在client模式下是比较好的选择
并行GC,对比串行GC即有多个线程同时执行GC任务
它的关注点在于如何达到一个可控制的吞吐量.
我们现在最常用的老年代GC,他的目标就是尽量减少”Stop the world(所有用户线程终止)”的情况发生,尽可能的提高性能.
缺点:
1. 对CPU非常敏感,因为为了提高GC效率,会使用多线程进行收集,就占用了CPU的资源,导致在垃圾收集时,JVM的性能降低比较多,尤其是在CPU核心数比较少的机器上,默认启用线程数(CPU数量+3)/4.
2. 无法处理浮动垃圾, 因为GC时是与用户线程并发的,所以有一部分垃圾会在GC过程中产生,无法在本次GC中回收.还有因为是在GC时必须预留内存资源给用户使用.CMS提供了一个GC时已经使用内存的参数来设置比例.
3. 内存碎片,CMS是基于标记清除算法,所以CMS必须进行碎片整理,不过为了减少”stop the world”的时间,CMS允许在多次GC以后再进行碎片整理.
G1是目前最新的收集器,它的堆内存结构虽然也有new\old之分,但不同的是他没有物理上的分区,这种分区完全是逻辑上的分区
G1的堆内存结构将分为n多小块(Region),根本原理就是减小单次GC时锁定内存块的大小,从而可以增加GC时与用户线程的并发性,减少停顿时间.
总的来说,G1能减少”stop the world”的时间,但是因为执行GC的总时间总次数(虽然是并发的)增加了,所以可能会降低一般情况下的吞吐量
GC算法有这么多,我们到底选用什么样的算法才合适?sun公司已经给出了答案,如果真的存在这种全能型选手(前面提到的G1可能会成为这种全能选手),也就不会有这么多的GC给我选择了。IBM的研究表明98%的对象都是朝生夕死,所以对于不同的对象Hot Spot
提供了不同的GC算法,以在高效和性能上进行平衡。
这就是为什么要对堆内存进行分代的原因
参数 | 描述 |
---|---|
Xmx | JVM最大可用内存 |
Xms | JVM初始化内存,一般将与Xmx设置一致,避免GC后内存重新分配 |
Xmn | 新生代分配大小,官方建议是占总的3/8,但是我们的一般应用,都是无状态应用,对象会被及时回收,所以可以将新生代设置更大一些 |
Xss | 线程栈大小,默认1024k(有的地方说默认128,那是老早的版本了),其实还是比较大的,除非你做了一个非常离谱的递归调用,不然绝对够用 |
XX:PermSize | 永久代大小,存储方法及常量,一般应用128M够用了,除非你有很多Proxy生成类 |
XX:MaxPermSize | 永久代的最大值 |
XX:ParallelGCThreads | 并行垃圾收集的线程数 |
XX:SurvivorRatio | Eden区 和S区 的比例,一般为8,怎eden:s0:s1=8:1:1,在实践中,对于我们的普通无状态应用,设置66536,即s区接近0,可以减少Monitor GC次数 |
XX:CMSInitiatingOccupancyFraction | 如果GC使用CMS GC,触发FullGC时,Old区已经使用大小百分比,如=80,则表示已经使用80% |
XX:+UseCMSCompactAtFullCollection | 因为老年代也是标记-清除算法,所以会产生内存碎片,这个参数设置每次FGC后进行内存整理压缩,显然会降低性能,延长GC时间。 |
XX:CMSFullGCsBeforeCompaction | 由于上一个原因,所以JVM提供了另一个参数,经过几次FGC才进行内存整理,这样就能平衡性能了 |
我们之前设置的SurvivorRatio非常大,也就是说年轻代只有Eden区而没有S区用来做对象复制,因为理论上我们都是无状态对象,经过GC后不存在对象需要反复GC而进入老年代的情况,但是通过实际测试来看,当高并发时,发生YGC时,大量对象被引用而无法被GC掉,这时候不存在S区,对象只能直接进入Old区,导致Old区瞬间爆满,从而发生FGC。所以最后我们将s区保留,同时减少Old区。增加s区自然会减小Eden区,从而增加YGC的频率,但是换来的却是高吞吐量。
最后的参数设置
-Xms6g -Xmx6g -Xmn4g -Xss1024K -XX:PermSize=128m -XX:MaxPermSize=256m -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=3 -XX:SurvivorRatio=6 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:CMSInitiatingOccupancyFraction=80