一、判断对象是否死亡
1.引用计数算法:
给对象中添加一个引用计数器。当对象实例每被引用一次计数器+1,每一个引用失效计数器-1;当计数器为0时,对象可被回收;
缺点:对象相互引用时,计数器的值永不为0;
2.可达性分析算法
通过一系列的"GC Roots"对象作为起点,从这些节点开始向下搜索,所走过的路程称为引用链,当一个对象没有与任何引用链相连,则证明对象不可用;
Java中可以作为GC Roots的对象
- 虚拟机栈中的引用对象
- 本地方法栈中的Native方法引用的对象
- 方法区中类静态属性的引用对象
- 方法区中常量的引用对象
3.回收方法区
- 回收方法区的废弃常量:当没有对象引用方法区中的常量时,常量被判定为废弃常量
- 回收无用的类:类的所有实例被收回,加载该类的ClassLoader被收回,该类对应的对象没有在任何地方被引用;
二、垃圾收集算法
1.标记 - 清除算法
标记过程如同以上判定对象死亡时的标记相同;
清除过程就是对标记对象进行删除的过程;
缺点:效率低。并且清除后产生大量不连续内存碎片。
2.复制算法
复制算法就是把内存分为两块,只使用其中的一块,当进行回收时把存活的对象复制到另一块内存中,然后在使用的那块内存进行垃圾回收。这样提高了效率,解决了内存碎片问题。
复制算法适用于新生代:在HotSpot虚拟机中新生代的内存分为8:1:1。使用8和1进行信息的存放,把存活的对象复制到剩下的1内存中。
空间不够时,依赖其他内存进行分配担保。
3.标记 - 整理算法
标记过程和上文相同。
整理过程是让存活对象都向一端移动,之后把端边界以外的内存直接清理。
标记 - 整理算法适用于老年代:对象存活率较高时,复制算法的效率就会变低。
老年代的存活率高,所以采用标记 - 整理算法的效果更好。
4.分代收集算法
分代收集算法就是对于不同对象的存活周期将内存划分成不同的块。例如JAVA堆中划分的新生代和老年代。根据特性采用不同的垃圾收集算法。
三、HotSpot虚拟机算法实现
- 枚举根节点:
问题:使用可达性分析算法进行垃圾收集,GC Roots遍历引用,耗时长。
在进行可达性分析要确保一致性,而此时的线程都在变化。
解决:使用准确式GC。在HotSpot中通过OopMap的数据结构来实现准确式GC。使用OopMap记录偏移量。 - 安全点:Java堆中发生变化的引用太多,如果记录每个OopMap空间成本太高。所以把让程序执行时间长的部分定义为安全区,记录下来。
实现:当GC需要中断线程时,设置一个标志,当线程执行到标志时,判断为真,当前线程挂起。 - 安全区域:当程序不执行,JVM中毒单请求,线程无法挂起。在安全区域中的引用关系不会发生变化,虚拟机可以在安全区域中完完整的执行GC。
四、垃圾收集器
-
CMS收集器:
CMS收集器用于回收老年代的对象。采用标记 - 清除算法。所以使用CMS收集器之后产生的内存空间是不规整的。
CMS收集器主要经过四个步骤实现:
初次标记、并发标记、重新标记、并发清除。
缺点:空间不规整。低于四核降低执行速度。无法处理浮动垃圾。 -
G1收集器:
G1收集器可以用于回收新生代和老年代。把内存划分成一块块Region区域。回收时从整体上看采用标记 - 整理算法。局部 Region采用复制算法。
G1收集器主要经过四个步骤实现:
初次标记、并发标记、最终标记、筛选回收。 -
Serial/Serial Old收集器:
单线程的收集器。在进行垃圾收集时暂停所有的线程,单线程的方式方式进行垃圾收集。新生代使用复制算法。老年代使用标记 - 整理算法。 -
ParNew/ParNew Old收集器:
使用多线性进行垃圾回收,其他的部分和Serial收集器类似。 -
Parallel Scavenge收集器:
"吞吐量优先"收集器。它和ParNew类似采用是新生代收集器,采用复制算法,并行多线程收集。其他的收集器注重于减短暂停线程的时间,但是Parallel Scavenge更注重达到一个可控制的吞吐量。
通过设置停顿时间和吞吐量大小来实现自适应调节。
五、内存分配与回收策略
- 对象在新生代的Eden区域进行分配内存。当Eden和Survivor空间不足时发生Minor GC回收对象。
把存活的对象利用复制算法分配进入另一个Survivor区域。如果Survivor不够存放存活对象,通过分配担保,对象进入老年代。 - 大对象对于虚拟机分配是坏消息,例如长字符串或者数组。可以通过设置参数使大对象直接进入老年代。
- 虚拟机给每个对象定义一个对象年龄计数器。从Eden出生开始进行一次Minor GC对象年龄计数器+1,当(默认)计数器达到15时进入老年代。
- 在Survivor区域中相同年龄的对象大小总和等于或超过Survivor容量的50%,那么对象直接进入老年代。