串行回收:指在同一时间段内只允许有一个 CPU 用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾回收结束
并行回收:和串行回收相反,并行收集可以运行在多个 CPU 同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然于串行回收一样,使用了 " Stop The World " 的机制
并发回收:并发式垃圾回收器于应用线程交替工作,以尽可能的减少应用程序的停顿时间
独占回收:独占式垃圾回收一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束
压缩式:压缩式垃圾回收器会在回收完成后,会对存活对象进行压缩整理,以此消除回收后的内存碎片(使用指针碰撞的方式进行内存分配)
非压缩式:非压缩式的回收器在回收完成后少了压缩整理这一步(使用空闲列表的分配方式)
根据分代的不用选用不同的垃圾回收器
虽然有这几种特点,当时各个垃圾回收器的特点基本都是这个几种的排列组合
红色的三项是这些指标里最重要的三项,同时也构成了 " 不可能铁三角 ",即三者的综合表项会随着技术的进步而越来越好,但是一款优秀的垃圾收集器最多只能满足其中两项
主要还是要抓住两点:
Java 的使用场景很多,包括服务器端、移动端等,所以就要根据不同的场景选择不同的 GC ,以此来提高垃圾回收的性能
这个收集器是单线程的,但这并不意味着它只能使用一个 CPU 或一条收集线程去进行垃圾回收,更重要的是它在执行垃圾回收时,必须暂停其他所有工作线程,直到它收集完成
优势:简单高效,对于限定单个 CPU 来说,Serial 收集器由于没有线程交互的开销,佐伊自然可以获得最高的效率
在用户的桌面应用中,可用内存一般不大,可以在较短时间内完成垃圾收集,只要不频繁发生就可以接受 目前这种垃圾收集器已经基本不再使用
如果说 Serial 是年轻代的单线程垃圾收集器,那么 ParNew GC 就是它的多线程版本
ParNew GC 是很多 JVM 在 Server 模式下新生代默认的垃圾收集器
对于新生代回收次数频繁,使用并行方式更加高效,对于老年代,回收次数少,使用串行方式更加节省资源(CPU 串行可以省去上下文切换的时间)
HopSpot 虚拟机中处理 ParNew GC 收集器是基于并行回收外,Parallel Scavenge 收集器也同样采用了复制算法、并行回收和 " Stop The World "的机制
并且与 ParNew GC 不同,Parallel Scavenge 收集器的目的则是达到一个可控制的吞吐量,因此它也被称为吞吐量优先的收集器。
高吞吐量可以高效的利用 CPU 时间,尽快完成程序任务,主要适合在后台运算而不需要太多交互的任务。因此,常见在服务器环境中使用。例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。
在吞吐量优先的应用场景中,Parallel 收集器和 Parallel Old 收集器的组合,在 Server 模式下的内存回收性能很不错
具体使用时的参数我这里就不列举了,兄弟们在使用的时候自行百度吧
CMS 是真正意义上的并行垃圾收集器,它在真正意义上实现了垃圾回收线程和用户线程同时工作
CMS 收集器的关注点尽可能缩短垃圾收集是用户线程的停顿时间,挺短时间越短就越适合和用户交互的程序,提高用户体验
CMS 也采用了标记-清除算法,并且也会 " Stop The World "
CMS 垃圾回收的过程也比之前复杂,主要分为四个阶段:
由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的,另外,由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
CMS收集器的垃圾收集算法采用的是标记—清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地将会产生一些内存碎片。那么CMS在为新对象分配内存空间时,将无法使用指针碰撞(Bump the Pointer)技术,而只能够选择空闲列表(Free List)执行内存分配。
既然使用标记-清除算法会产生内存碎片,那为什么不可以使用 Mark Compact 算法呢?
因为并发清楚的时候,使用 Compact 算法的话,原来的用户线程就不能使用了(用户线程资源的地址发生了改变)所以不能用 Compact 算法
优点: 并发收集、低延迟
缺点:
明明前面已经有了那么多功能强大各异的 GC ,还要发不 Garbage First (G1)GC 呢?
原因就在于应用程序所面对的业务经常越来越庞大、复杂、用户越来越多,没有 GC 就不能保证应用程序正常运行,而经常造成 STW 的 GC 又跟不上实际需求,所以才会对 GC 进行不断的改造尝试
它把堆内存分割为很多不相关的区域(Region)(物理上不连续的),并且有计划地避免在整个Java 堆中进行全区域的垃圾收集。G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(Garbage First)。
G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征
与其他收集器相比,G1 采用了全新的分区算法,其具体的表现如下:
在 G1 回收垃圾阶段,可以有多个 GC 线程同时工作,有效利用多核计算能力。此时用户 STW
从并发性上来说,G1 拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,不会在整个回收阶段发生完全阻塞应用程序的情况
从分代上来看,G1 依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有 Eden 区和 Survivor 区。但从堆的结构上看,它不要求整个 Eden 区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量 它将堆空空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代。在进行垃圾回收时,它同时兼顾这些区域
优势:
缺点:
相较于CMS,G1还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比CMS要高
从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间
适用场景:
面向服务端应用,针对具有大内存、多处理器的机器。(在普通大小的堆里表现并不惊喜)
最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案;
如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;(G1通过每次只清理一部分而不是全部的Region的增量式清理来保证每次GC停顿时间不会过长)。
用来替换掉JDK1.5中的CMS收集器;
在下面的情况时,使用G1可能比CMS好:
① 超过50%的Java堆被活动数据占用;
② 对象分配频率或年代提升频率变化很大;
③ GC停顿时间过长(长于0.5至1秒)。
HotSpot 垃圾收集器里,除了G1以外,其他的垃圾收集器使用内置的JVM线程执行GC的多线程操作,而G1 GC可以采用应用线程承担后台运行的GC工作,即当JVM的GC线程处理速度慢时,系统会调用应用程序线程帮助加速垃圾回收过程
分区 Region :化整为零
使用 G1 收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。可以通过-XX:G1HeapRegionSize设定。所有的Region大小相同,且在JVM生命周期内不会被改变。
虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。同时,一个 region 有可能属于 Eden,Survivor 或者 Old/Tenured 内存区域。但是一个region只可能属于一个角色。 G1 垃圾收集器还增加了一种新的内存区域,叫做 Humongous 内存区域,主要用于存储大对象,如果超过1.5个region,就放到 H 区。
*设立 H 区的原因:*对于堆中的大对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放大对象。如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。为了能找到连续的H区,有时候不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分来看待
G1 GC 垃圾回收的过程:
主要包括三个环节:年轻代 GC 、老年代并发标记过程、混合回收 (如果需要,单线程、独占式、高强度的 Full GC 还是继续存在的,它针对 GC 的评估失败提供了一种保护机制,即强力回收)
应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程;G1的年轻代收集阶段是一个并行的独占式收集器。在年轻代回收期,G1 GC暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到Survivor区间或者老年区间,也有可能是两个区间都会涉及。
当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程。
标记完成马上开始混合回收过程。对于一个混合回收期,G1 GC从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了。同时,这个老年代Region是和年轻代一起被回收的
过程一:年轻代 GC
JVM启动时,G1先准备好Eden区,程序在运行过程中不断创建对象到Eden区,当Eden空间耗尽时,G1会启动一次年轻代垃圾回收过程。年轻代垃圾回收只会回收Eden区和Survivor区。
YGC时,首先G1停止应用程序的执行(Stop-The-World),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区所有的内存分段
然后开始如下回收过程:
第一阶段,扫描根。
根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同RSet记录的外部引用作为扫描存活对象的入口。
第二阶段,更新RSet。
处理dirty card queue(见备注)中的card,更新RSet。此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用
第三阶段,处理RSet。
识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。
第四阶段,复制对象。
此阶段,对象树被遍历,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到Old区中空的内存分段。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间。
第五阶段,处理引用。
处理Soft,Weak,Phantom,Final,JNI Weak 等引用。最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。
过程二:并发标记
过程三:混合回收
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region。这里需要注意:是一部分老年代,而不是全部老年代。可以选择哪些Old Region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC
过程四:Full GC
G1的初衷就是要避免Full GC的出现。但是如果上述方式不能正常工作,G1会停止应用程序的执行(Stop-The-World),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。
要避免Full GC的发生,一旦发生需要进行调整。什么时候会发生Full GC呢?比如堆内存太小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到full gc,这种情况可以通过增大内存解决。
导致G1Full GC的原因可能有两个:
今天的内容就是这些啦,大家一定要仔细观看哦