在JAVA里,可作为GC Roots的对象包括:
1、虚拟机栈(栈帧中的本地变量表)中的引用的对象
2、方法区中的类静态属性引用的对象
3、方法区中的常量引用的对象
4、本地方法栈中JNI的引用的对象
无用的类判定条件
1、该类所有的实例都已经被回收
2、加载该类的classloader已经被回收
3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集算法
标记-清除算法
分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另一个是空间问题,标记清除后会产生大量不连续的内存碎片。
复制算法
为了解决效率问题,复制算法将内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后把已使用过的内存空间一次清理掉。将内存分为一块较大的Eden空间和两块较小的survivor空间,每次使用Eden和其中的一块Survivor,当回收时,将Eden和Survivor中还存活着的对象一次性copy到另外一块survivor空间上,然后清理掉Eden和刚才使用过的Survivor空间。Eden:Survivor=8:1
标记-整理算法
复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低,更关键的是如果不想浪费50%的空间,就需要额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。根据老年代的特点,有人提出标记-整理算法,标记过程与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
分代收集算法
根据新生代老年代的特点采用最适当的算法。
垃圾收集器
垃圾收集器Serial收集器
新生代的单线程收集器,用一个线程完成垃圾收集工作,在收集期间stop the world,直到它收集结束。Serial收集器对于 运行在client模式下是一个很好的选择。
Serial/Serial Old收集器运行示意图ParNew收集器
Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、STW、对象分配规则、回收策略都与Serial收集器完成一样。运行在server模式下虚拟机首选的新生代收集器
ParNew/Serial Old收集器运行示意图Parallel Scavenge收集器
新生代收集器,也是使用复制算法并行的多线程收集器,它的关注点与其他收集器不同,CMS等关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Par Scavenge的目标则是达到一个可控制的吞吐量。吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间)
Serial Old收集器
Serial收集器的老年代版本,单线程,使用标记-整理算法,Client模式下虚拟机使用。如果在server模式下,主要有两大用途:一个是与Parallel Scavenge收集器搭配使用,另一个就是作为CMS收集器的后备预案,在并发收集发生concurrent mode failure时使用。
Parallel Old收集器
Parallel Scavenge收集器的老年代版本,多线程标记-整理算法
Parallel Scavenge/Parallel Old收集器运行示意图CMS收集器
一种以获取最短回收停顿时间为目标的收集器。运行过程分为4个步骤
1、初始标记
2、并发标记
3、重新标记
4、并发清除
其中初始标记、重新标记这两步骤仍然需要STW,初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器都可以与用户线程一起工作,所以总体来说CMS的内存回收过程是与用户线程一起并发地执行的。
CMS运行示意图G1收集器
G1收集器是基于标记-整理算法实现的收集器,不产生空间碎片,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。G1将整个JAVA堆划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程序,在后台维护一个优先表,每次根据允许的收集时间,优先回收垃圾最多的区域。
大多数情况下,对象优先在Eden分配,当Eden区没有足够的空间进行分配时将发起一次Minor GC。
大对象直接进入老年代,虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配 。这样做的目的是避免在Eden及两个Survivor区之间发生大量的内存拷贝。
网友评论