2、按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器
3、按碎片处理方式分,可分为压缩式垃圾回收器和非压缩式垃圾回收器:
4、按工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器
垃圾收集机制是Java的招牌能力,极大地提高了开发效率。那么,Java常见的垃圾收集器有哪些?
有了虚拟机,就一定需要收集垃圾的机制,这就是Garbage Collection, 对应的产品我们称为Garbage Collector。
• 一xx:+PrintCommandLineFlags: 查看命令行相关参数(包含使用的垃圾收集器)
• 使用命令行指令: jinfo 一flag相关垃圾回收器参数进程ID
/**
* -XX:+PrintCommandLineFlags
*
* -XX:+UseSerialGC:表明新生代使用Serial GC ,同时老年代使用Serial Old GC
*
* -XX:+UseParNewGC:标明新生代使用ParNew GC
*
* -XX:+UseParallelGC:表明新生代使用Parallel GC
* -XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC
* 说明:二者可以相互激活
*
* -XX:+UseConcMarkSweepGC:表明老年代使用CMS GC。同时,年轻代会触发对ParNew 的使用
*/
public class GCUseTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
while(true){
byte[] arr = new byte[100];
list.add(arr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出:
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
jdk8环境下,默认使用 Parallel Scavenge(新生代)+ Serial Old(老年代)
CMS整个过程比之前的收集器要复杂,整个过程分为4个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段。
初始标记(Initial一Mark) 阶段:在这个阶段中,程序中所有的工作线程都将会因为. “Stop一the一World"机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GCRoots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用.线程。由于直接关联对象比较小,所以这里的速度非常快。
并发标记(Concurrent一Mark)阶段:从GC Roots的 直接关联对象开始遍历整个对 象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
重新标记(Remark) 阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。
并发清除( Concurrent一Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
尽管CMS收集器采用的是并发回收(非独占式),但是在其初始化标记和再次标记这两个阶段中仍然需要执行“Stop一the一World”机制暂停程序中的工作线程,不过暂停时间并不会太长,因此可以说明目前所有的垃圾收集器都做不到完全不需要“Stop一the一World”,只是尽可能地缩短暂停时间。
由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。
另外,由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial 0ld收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
CMS收集器的垃圾收集算法采用的是标记一清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地将会产生一些内存碎片。 那么CMS在为新对象分配内存空间时,将无法使用指针碰撞(Bump the Pointer) 技术,而只能够选择空闲列表(Free List) 执行内存分配。
有人会觉得既然Mark Sweep会造成内存碎片,那么为什么不把算法换成Mark Compact呢?
答案其实很简答,因为当并发清除的时候,用Compact整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact更适合“Stop the World”这种场景”下使用。
• 并发收集
• 低延迟
1)会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发Full GC。
2) CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
3) CMS收集器无法处理浮动垃圾。可能出现“Concurrent Mode Failure" 失败而导致另一次Full GC的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将 无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。
• JDK9新特性: CMS被标记为Deprecate了(JEP291)
• 如果对JDK 9及以上版本的HotSpot虚拟机使用参数一XX:+UseConcMarkSweepGC来开启CMS收集器的话,用户会收到一个警告信息,提示CMS未来将会被废弃。
• JDK14新特性: 删除CMS垃圾回收器(JEP363)
• 移除了CMS垃圾收集器,如果在JDK14中使用一XX: +UseConcMarkSweepGC的话,JVM不会报错,只是给出一个warning信息,但是不会exit。JVM会自动回退以默认GC方式启动JVM
既然我们已经有了前面几个强大的GC,为什么还要发布Garbage First (G1)GC?
原因就在于应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有GC就不能保证应用程序正常进行,而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化。G1 (Garbage一First) 垃圾回收器是在Java7 update4之后引入的一个新的垃圾回收器,是当今收集器技术发展的最前沿成果之一。
与此同时,为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time) ,同时兼顾良好的吞吐量。
官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起“全功能收集器”的重任与期望
与其他GC收集器相比,G1使用了全新的分区算法,其特点如下所示:
• 相较于CMS,G1还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint) 还是程序运行时的额外执行负载(overload) 都要比CMS要高。
• 从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用,上则发挥其优势。平衡点在6一8GB之间。
使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB, 2MB, 4MB, 8MB, 1 6MB, 32MB。可以通过一 XX:G1HeapRegionSize设定。所有的Region大小相同,且在JVM生命周期内不会被改变。
虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。
G1 GC的垃圾回收过程主要包括如下三个环节:
(如果需要,单线程、独占式、高强度的Full GC还是继续存在的。它针对GC的评估失败提供了一种失败保护机制,即强力回收。)
顺时针, young gc 一> young gc + concurrent mark 一> Mixed GC顺序,进行垃圾回收。
当越来越多的对象晋升到老年代oldregion时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC, 该算法并不是一个0ldGC,除了回收整个Young Region,还会回收一部分的0ldRegion。这里需要注意:是一部分老年代, 而不是全部老年代。可以选择哪些0ldRegion进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC。
G1的初衷就是要避免Full GC的出现。但是如果上述方式不能正常工作,G1会停止应用程序的执行(Stop一 The一World),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。
要避免Full GC的发生,一旦发生需要进行调整。什么时候会发生Full GC呢?比如堆内存太小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到full gc, 这种情况可以通过增大内存解决。
导致G1Full GC的原因可能有两个:
• 1.Evacuation的时候没有足够的to一 space来存放晋升的对象;
• 2.并发处理过程完成之前空间耗尽。
从Oracle官方透露出来的信息可获知,回收阶段(Evacuation)其实本也有想过设计成与用户程序一起并发执行,但这件事情做起来比较复杂,考虑到G1只是回收一部分Region, 停顿时间是用户可控制的,所以并不迫切去实现,而选择把这个特性放到了G1之后出现的低延迟垃圾收集器(即ZGC)中。另外,还考虑到G1不是仅仅面向低延迟,停顿用户线程能够最大幅度提高垃圾收集效率,为了保证吞吐量所以才选择了完全暂停用户线程的实现方案。
一个对象被不同区域引用的问题(分代引用问题)
一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确?
在其他的分代收集器,也存在这样的问题( 而G1更突出)。回收新生代也不得不同时扫描老年代?这样的话会降低MinorGC的效率。
解决方法: