JVM 常用垃圾收集算法和垃圾收集器

垃圾收集算法

标记-清除算法(Mark-Sweep)

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。

缺点
1、 标记和清除效率都不高
2、 清除后会产生大量的不连续内存碎片,碎片太多,导致程序需要为大对象分配内存时无法找到足够的连续内存不得不触发另一次垃圾收集

标记-整理算法

标记整理在标记清除的基础上做了改进,第一阶段标记阶段,同标记清除的标记,标记出所有需要回收的对象,但是第二阶段整理阶段是: 在标记完成后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象。

相比于标记清除,优点是内存整理后不会产生大量不连续的内存碎片

对象存活率高的情况下使用标记-整理算法效率会大大提高。

复制算法

为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小
的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用
的内存清掉

优点:
实现简单,内存效率高,不易产生空间碎片

缺点:
很明显,需要预留一般的内存作为复制对象的空间,导致可用内存只有原来的一半
对象存活率高的情况下就要执行较多的复制操作,效率将会变低

分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存
划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young
Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃
圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

新生代的复制算法

Eden/S0/S1默认空间比例Eden:S0:S1为8:1:1,有效内存(即可分配新生对象的内存[Eden和Survivor1])是总内存的90%。Eden/S0/S1三个空间的比例为8:1:1,则可能会出现Eden+S0中存活对象超过了总空间的10%(S1、S0的空间都是总空间的10%),在这种情况下,新生代GC会将存活周期长的对象直接放入老生代,而无需达到我们设置的阈值(转入老生代的存活次数,-XX:MaxTenuringThreshold)

算法过程

Eden+S0可分配新生对象;
    对Eden+S0进行垃圾收集,存活对象复制到S1。清理Eden+S0。一次新生代GC结束。
Eden+S1可分配新生对象;
    对Eden+S1进行垃圾收集,存活对象复制到S0。清理Eden+S1。二次新生代GC结束。
循环1。

HotSpot实现复制算法的流程:

1、当Eden区满的时候,会触发第一次Minor gc,把还活着的对象拷贝到Survivor From区;
2、当Eden区再次触发Minor gc的时候,会扫描Eden和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,直接赋值到To区域(如果出现To区域剩余空间不够复制原有存活对象的情况,则通过老年代进行内存担保,把这些对象直接复制到老年代,不用等待MaxTenuringThreshold),并将Eden和From区域清空。
3、当后续Eden又发生Minor gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
4、部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终还是存活,就存入老年代。

Stop-the-world

当Stop-the-world发生时,除了GC所需的线程以外,所有线程都处于等待状态,直到GC任务完成。GC优化很多时候就是指减少Stop-the-world发生的时间。
意味着 JVM 因为要执行GC而停止了应用程序的执行
MinGC\MajorGC都属于Stop-the-world, 那为什么MajorGC耗时较长呢?因为OldGen包含了大量存货下来的对象。

MinorGC

新生代GC Young GC
清理年轻代(Eden和Survivor区)
从年轻代空间(主要是 Eden区)回收内存被称为 Minor GC。
新生代(主要指Eden区)的Minor GC 比较常见,各个收集器均支持。

触发条件:
eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。
当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。
Survivor满不会触发Minor GC

新生代分为三个区域,eden space, from space, to space。默认比例是8:1:1。在MinorGC时,会把存活的对象复制到to space区域,如果to space区域不够,则利用担保机制进入老年代区域。

所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。

执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。

MajorGC

老年代GC Old GC
出现Major GC通常会出现至少一次Minor GC。
清理老年代
目前,只有CMS收集器会有单独收集老年代的行为。

FullGC

针对整个新生代、老年代、元空间(metaspace, java8以上版本取代永久代)、堆外内存的全局范围的GC,
注意 FullGC 不等于Major GC , 也不等于 Minor GC + Major GC ,取决于什么样的垃圾收集器组合

触发条件

1、当年老代满时会引发Full GC,Full GC将会同时回收新生代、年老代 ;
2、当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载 。
3、调用System.gc时,系统建议执行Full GC,但是不一定会执行
4、通过 Minor GC 后进入老年代的空间大于老年代的可用内存
5、由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小 

注意,很多时候,Major GC 会和Full GC混淆使用,需要具体分辨是老年代的回收还是整堆回收。

Mixed GC

指目标是收集整个新生代以及部分老年代的垃圾收集。
目前只有G1收集器会有这种行为

类卸载的条件

1. 该类所有的实例已经被回收
2. 加载该类的ClassLoder已经被回收
3. 该类对应的java.lang.Class对象没有任何对方被引用

垃圾收集器:

新生代垃圾收集器

Serial

单线程 STW 回收(暂停所有用户线程)  Client 模式下的虚拟机   新生代   复制算法

ParNew

多线程 STW 回收(暂停所有用户线程)  Server 模式下的虚拟机   新生代   复制算法

Parallel Scavenge

多线程 STW 回收(暂停所有用户线程)  Server 模式下的虚拟机   新生代   复制算法
目标是达到一个可控的吞吐量(吞吐量=运行用户代码时间/(运行用户代码的时间+垃圾收集的时间))  吞吐量优先垃圾器
自适应的调节策略(自动根据系统运行情况收集性能监控信息,动态调整(新生代大小、Eden与Survivor的比例,晋升老年代对象的年龄等)以提供合适的停顿时间或者最大吞吐量)

老年代垃圾收集器

Serial Old

单线程 STW 回收(暂停所有用户线程)  Client 模式下的虚拟机   老年代   标记整理

Parallel Old

是Parallel Scavenge 收集器的老年代版本,
多线程 STW 回收(暂停所有用户线程)  Server 模式下的虚拟机   老年代   标记整理

CMS

Concurrent Mark Sweep
一种获取最短回收停顿时间为目标的收集器
多线程    老年代   标记清除   CUP敏感  默认启动回收线程数= (cpu+3)/4  无法清除浮动垃圾

1、初始标记 STW  仅仅标记GC Roots能直接关联的对象,速度很快
2、并发标记      用户线程和GC线程并行执行
3、重新标记 STW
4、并发清除      用户线程和GC线程并行执行

G1

1、初始标记(Initial Marking)
仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,
能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,
而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。 
2、并发标记(Concurrent Marking)
从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,
但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理SATB记录下的在并发时有引用变动的对象。 ·
3、 最终标记(Final Marking):
对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录。 
4、筛选回收(Live Data Counting and Evacuation)
负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,
根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,
然后把决定回收的那一部分Region的存活对象复制到空的Region中,
再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。

你可能感兴趣的:(JVM 常用垃圾收集算法和垃圾收集器)