垃圾收集器
不同的垃圾收集器性能以及专长有区别,用户可以通过关键字根据自己的需求自己组合使用垃圾收集器。
垃圾收集器在工作时,因为需要Stop the world 停止用户的线程,仅做垃圾收集这一件工作
但是要理解垃圾收集器里面的多线程,即并发。
并行:指垃圾收集器多线程同时工作,收集的速度更快,但是此时用户的线程还是停止的。
并发:指的是垃圾收集器的线程跟用户的线程同时进行,垃圾收集器工作时用户的线程并不停止。
Serial收集器
收集新生代,单线程、高效率,在Client模式下面是默认的新生代收集器。
ParNew收集器
是Serial收集器的多线程版本,在Server模式下是默认的新生代收集器。
新生代的收集器因为对象的存活时间短,所以使用的是复制的算法。
Parallel Scanvenge收集器
新生代、多线程,使用复制算法。那么跟Parnew收集器的区别在哪里呢?
因为其他收集器主要关注的时间是如何把垃圾收集器工作的时间变短,这样用户的线程停顿的时间就会够短,而Parallel Scanvenge收集器关注的主要控制一个合理的吞吐量。
吞吐量: 运行用户代码的时间/运行用户代码时间+垃圾收集器工作的时间。
因此,如果垃圾收集器的时间短,那么就很适合跟用户交互的程序,良好的响应速度可以提升用户体验,而高吞吐量可以高效率地利用CPU,适合在后台运算但不需要太多交互的任务。
Serial-old收集器
Serial-old是Serial收集器的老年代版本,使用标记-整理算法,单线程。
Parallel-old收集器
是Parallel的老年代收集版本,多线程,关注吞吐量,标记-整理算法。
CMS收集器
CMS收集器是为了实现最短回收停顿时间的收集器,基于标记-清除算法,其工作过程经过四个阶段:
初始标记
并发标记
重新标记
并发清除
初始标记是为了标记一下GC Root能直接关联到的对象(很快),并发标记是进行GC Root Tracing的过程,重新标记阶段是为了修正并发标记阶段因为用户程序运行修改的那部分对象的标记记录,这个阶段的停顿时间会比初始标记时间长,但是远比并发标记的时间要短。
在这四个过程中,最花费时间的并发标记以及并发清除过程是跟用户线程一起工作的,因此从总体上来说,CMS收集器是跟用户线程并发工作的。
内存分配策略
之前的学习中,我们已经大概知道,JAVA虚拟机会把对象分为新生代以及老年代,新生代对应的是寿命较短的对象,老年代对应的是寿命较长的对象,顾名思义,新生代的特点就是寿命短,在系统中不算稳定,因此可能会有很多需要被回收,因此使用的大多是复制的算法。Minor-GC。
老年代因为其被回收的比例不高,因此大概率使用的都是标记-清除、标记-整理的算法,但是因为标记-清除的算法会形成很多小的内存碎片,如果老年代的内存大小太大,会造成多次数的Full-GC。
那么对象分配究竟按照什么样的原则分配区域呢?
1.对象优先分配在Eden区域
大多数情况,对象是分配在Eden区域的,当Eden区域快满时,虚拟机会发起一次Minor Gc,新生代区域分为两个部分Eden以及Survivor,可以通过指令设置Eden以及Survivor区域的比例。
2.大对象直接分配在老年代
大对象指的是需要连续空间分配的JAVA对象,需要的内存很大,会直接分配到老年代。
3.长期存活的新生代会进入老年代
虚拟机给每一个对象都定义了一个Age计数器,如果一个对象经历过一次Minor Gc存活并且Survivor区域可以放下该对象,会进入到Survivor区域 ,并且Age+1,如果该对象Age大于新生代的年限,会转移到老年代Tenur区域中。 该新生代年纪的上限可以通过指令来设置。
4.动态的对象年龄判定
并不是只有Age达到上限对象才会进入老年区,如果某一个年龄的对象在Survivor区中的内存已经超过一半,那么该年龄的对象会直接进入老年区。
5.空间分配担保
每次Minor Gc之前,虚拟机都会检查老年代的剩余空间是否大于新生代的空间,如果这个成立,那么Minor Gc是安全的,如果不成立,那么虚拟机会检查现在的参数设置是否允许冒险,如果现在参数允许冒险,那么虚拟机会检查以往进入老年代对象的内存大小是否小于现在老年代的最大连续内存,如果小于,那么虚拟机会尝试进行一次Minor Gc,如果不小于或者参数设置不允许冒险,那么虚拟机会进行一次Full Gc。
通常会设置该参数开启,因为这样可以尽量避免收集器进行消耗巨大的Full Gc。