一、堆的结构
一个对象被创建后一般是会放在eden区的,但也有例外,比如局部变量会在栈上分配,超过一定阈值的大对象会直接放在老年代。s0和s1是幸存代,使用复制算法来复制对象,所以s0和s1的内存大小是一样的。当对象经过多次垃圾回收还存在的话就会被标记为老年对象,会将该对象放入到老年代(tenured)中。
二、串行收集器
最古老,最稳定的垃圾回收器,效率比较高
缺点是可能会产生较长时间的停顿,因为只使用一个线程来进行垃圾回收。
-XX:+UserSerialGC:
新生代、老年代使用串行回收
新生代使用复制算法
老年代使用标记压缩算法,收集器会将所有可达对象标记,将可达对象将内存一端复制,然后将边界对象之外的对象都清除掉。
三、并行收集器
ParNew收集器,新生代并行收集器
-XX:+UseParNewGC
从名称上可以看出是新生代的回收机,该收集器只对新生代有效,老年代仍然使用串行回收器。在多核服务器上性能较好。
-XX:ParallelGCThreads 限制线程的数量
Parallel收集器
类似ParNew算法,与该算法不同的是Parallel更加关注吞吐量。该算法新生代依然使用复制算法,老年代使用标记-压缩算法。
-XX:+UseParallelGC:使用Parallel收集器+老年代串行
-XX:+UseParallelOldGC:使用Parallel收集器+老年代并行
-XX:MaxGCPauseMills:最大停顿时间,单位毫秒,GC尽力保证回收时间不超过设定值
-XX:GCTimeRatio:垃圾收集时间占总时间的比,这里的时间是指CPU的时间,默认是99,即最大允许1%的时间用来做GC
以上两个参数是矛盾的,因为停顿时间和吞吐量不能同时调优。要想停顿时间短,则吞吐量就要小,即分配给GC使用的资源要少。所以需要平衡这两者的关系,达到一个最优的状态。
四、CMS收集器
Concurrent Mark Sweep并发标记清除。使用的是标记-清除算法。第一步是标记垃圾对象,第二部是进行清除。并发是指和应用程序线程一起执行。停顿时间会比其他算法较少。停顿减少,并发阶段会降低应用程序的吞吐量。该收集器只是老年代的收集器,不会对新生代启作用。
-XX:+UseConcMarkSweepGC
CMS的运行过程可以分为以下5个步骤,它着重实现了标记的过程:
1.初始标记
标记根可以直接关联到的对象,该过程速度很快,但是这个过程会发生全局停顿
2.并发标记
是主要的标记过程,标记全部的对象,这个过程是和用户线程一起运行的,不会发生停顿,用户线程可以正常的运行。
3.重新标记
由于并发标记时,用户线程依然在工作,因此GC在正式清理前,再做修正。该过程也会发生全局停顿。
4.并发清除
基于以上3步标记的结果,直接清理对象。
5.并发重置
为下一次垃圾收集做准备,把一下收集器内部的数据结构、对象进行重置,这样能更好的进行下一次的收集。
特点:
尽可能的降低停顿,会影响系统整体吞吐量和性能,因为在用户线程运行过程中,会分一部分的CPU去做GC,系统性能在GC阶段,反应速度就下降一半,吞吐量就相应的下降了。垃圾的清理不彻底,因为在清理阶段,用户线程还在运行,这又会产生新的垃圾,无法正常清理。
用户CMS线程是和用户线程一起运行,因此不能在堆空间快满的时候再清理,否则应用程序会分配不到可用空间。如果内存预留空间不够,就会引发Concurrent mode failure的错误。所以一般需要通过-XX:CMSInitiatingOccupanyFraction来设置GC的阈值。一旦遇到Concurrent mode failure错误,jvm会改用串行回收器作为后备,这是后可能会发生较长时间的全局停顿。
标记-清除和标记-压缩对比
标记-清除算法由于只做垃圾清除,不做压缩,因此执行结束会产生大量的内存碎片,这会导致有些对象在申请连续内存空间的时候申请不到资源,而可用内存又比较多。标记-压缩算法由于会对内存进行整理,因此不存在这种情况。
为解决这个问题,CMS使用下面几个参数在GC后对内存进行整理:
-XX:+UseCMSCompactAtFullCollection:在Full GC后进行一次内存整理,整理过程是独占的,需要发生全局停顿,原因是需要移动可用对象的位置,有可能引起停顿时间变长。
-XX:+CMSFullGCsBeforeCompaction:设置进行多少次Full GC后,进行一次碎片整理。
-XX:ParallelCMSThreads:设置CMS的线程数量,该值一般设置为约等于CPU的可用核数,不宜设置的过大。
五、Tomcat参数设置
一般要减少GC的次数和堆内存的扩展,我们可以将堆内存设置的比较大,在GC压力比较大的时候使用并行垃圾回收算法也能提高吞吐量。另外,jdk的版本也能影响系统的吞吐量,高版本的jdk一般会带来性能的提升,但是升级jdk版本需要经过充分的测试才行,否则会出现不可知的错误。