一.如何确定某个对象是垃圾
两种方法:1.引用计数法;2.可达性分析
1.引用计数法
原理:给对象添加一个引用计数器,引用增加1个,则计数加1,减少一个引用,计数减1。垃圾回收时,只收集计数为0的对象;优点:实现简单,判断高效,可以解决大部分场景;缺点:无法解决循环引用的问题;例如:、
public static void main(String[] args) { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; }
另外,频繁大量的引用,带来巨大的计算开销;
2.可达性分析
原理:通过一系列"GC Roots"对象作为起始点,开始向下搜索;所有搜索过的路径称为“引用链”,当GC Root到一个对象不可达时,则证明这个对象是不可用的
GC Roots包括:
所有当前被加载的类;
java类的引用类型静态变量
所有当前被调用的方法的引用类型的参数、局部变量、临时变量等
可达性分析的优点:更加准确和精细,可以分析出循环数据结构相互引用的情况;缺点:实现复杂,需要分析大量的数据,消耗大量的时间,分析过程需要GC停顿(因为引用关系不能发生变化),所以需要停止所有java执行线程;
二.典型的垃圾回收算法
1.标记-清除
算法分成两个阶段执行,标记和清除。标记阶段找出所有需要被回收的对象,清除阶段回收被标记对象的内存占用空间,如图所示
从图中可以看出,该算法的最大问题是,内存碎片化严重,后续的大对象可能找不到合适的内存空间
2.复制算法
为了解决上面标记清除算法的碎片问题,提出了复制算法,将内存划分成大小相等的两块,每次只使用其中的一块,当该块满后,将尚存活的对象复制到另一块上去,然后把使用的内存清空掉,如图
优点是效率比较高,不易产生碎片,但是最大的问题是内存使用的只有一半,压缩了内存的使用率,存活对象增多的话,复制的效率会大大减少。
3.标记-整理算法
结合上上面两个算法的特点。标记阶段和标记清理算法一样,标记之后,在整理阶段,会把所有尚且使用的对象移动到一端,然后清除界限外的内存空间;
4.分代收集算法
分代收集算法是目前JVM常用的垃圾收集算法;根据对象存活的不同生存周期将内存划分为不同的区域,一般情况下,将GC划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时,只有少量的对象被回收,新生代的特点是每次回收时,都有大量的垃圾需要回收;因此不同的区域采用不同的垃圾回收算法;
1>对于新生代采用复制算法,因为新生代中需要回收的对象比较多,复制的对象比较少,但通常不是根据1:1的比例来划分新生代。一般是将新生代划分为一块较大的Eden空间和两个比较小的Survivor空间(From Space,To Space),每次使用Eden和其中的一块Survivor,回收垃圾时,将Eden和Survivor中存活的对象复制到另外一块Survivor中。如图所示
老生代每次只回收很少的对象,所以采用标记整理算法;当新生代的Eden Space和From Space空间不足时,发生一次GC,把这两个区中存活的对象放到To Space区域中,然后将Eden Space和From Space区进行清理,如果To Space无法存储某个对象,则将这个对象存储到老生代,之后继续使用Eden Space和From Space区,如此循环反复;当对象在Survivor中躲过一次GC,年龄加1,默认达到15后,对象被移到老生代中。
三.垃圾收集器
1.串行收集器
使用单线程处理所有垃圾回收工作,无需多线程交互,效率较高,适用于单处理及其,小数据集。
2.并行收集器
几个概念,最大垃圾回收暂停:指定垃圾回收时最长暂停时间,设定此值会减少系统的吞吐量;
吞吐量:指的是垃圾回收时间与非垃圾回收时间的比值,公式为1/(1+N),19表示5%的时间用于垃圾回收,默认99,即1%的时间用于垃圾回收
3.并发收集器
可以保证大部分工作并发进行(应用不停止),垃圾回收只占用较短的时间,适用于对响应比较高的系统,在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。