JVM垃圾收集器

主要讨论针对于Java堆的垃圾回收


如何判断对象需要回收

1. 引用计数法

给对象添加一个计数器,每当有一个地方引用它,计数器加1;当引用失效时,计数器减1;任何时刻计数器为0的对象就是不可能在被使用的。

但这种方法很难解决对象之间的相互循环引用问题,如下代码所示:

public class DemoClass {
	
	public Object instance = null;

	public void method() {
		DemoClass obj1 = new DemoClass();
		DemoClass obj2 = new DemoClass();

		obj1.instance = obj2;
		obj2.instance = obj1;

		obj1 = null;
		obj2 = null;

	}
}

obj1 和 obj2 的instance相互引用,所以他们的计数器都不为0,不能被回收。但是实际上这个两个对象已经不可能再被访问,需要被回收。

2. 根搜索算法

Java使用根搜索算法。 这个算法的基本思想为,通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,则此对象不可用,需要被回收。


如上图所示与GC Roots相关的Object 1、Object 2等可以存活。Object 5、Object 6、Object 7虽互有关联,但是没有链接到GC roots,所以会被判定为可回收对象。这就解决了上文中,对象之间的相互循环引用问题。

Java中可以作为GC Roots的对象包括以下几种,这些对象都是确定有用,需要存活的:

- 虚拟机栈(栈帧本地变量表)、本地方法栈(Native方法)中引用的对象。

- 方法区的静态属性常量引用的对象。


引用的四种类型

- 强引用 : 最普遍存在,类型“Object obj = new Object ()”,只要强引用还在,垃圾收集器永远不会回收掉被引用对象。

- 软引用 : 当发生内存溢出异常之前,收集器会二次回收软引用的对象。如果仍没有足够内存,才会抛出异常。

- 引用 : 弱引用关联对象只能生存到下次垃圾收集之前。

- 虚引用 : 完全不会对其生存时间构成影响,也无法通过虚引用取得对象实例。唯一目的是希望这个对象被回收时收到一个系统通知。

引用分级可以更灵活的使用内存空间。例如我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。


分代收集算法

当前商业虚拟机的垃圾收集都采用分代收集算法。分代收集算法根据对象的存活周期的不同将内存划分为几块。一般把java堆分为新生代和老年代。在新生代中,每次垃圾收集时都会有大批的对象死去,只有少量存活,所以采用复制算法,针对少量的存活对象进行复制收集。而在老年代中对象的存活率高,只需要去除少量死去的对象,所以使用“标记-清理”或“标记-整理”算法进行回收。

下图是使用分代收集算法Java堆的概念模型:


年轻代的GC

JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。一般情况下,新创建的对象会被分配到Eden区,这些对象经过第一次Minor GC(针对年轻代的垃圾回收)后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

在每次Minor GC发生之前,对象只存储在Eden、From区。紧接着进行GC,Eden区中所有存活的对象(上图红色的部分)都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。

担保机制:Minor GC期间,如果对象太大无法放入Survivor空间,通过分配担保机制提前转移到老年代。

大对象直接进入老年代:需要大量连续内存空间的Java对象,可以直接进入老年代。目的是避免Eden区及Survivor区之间大量的内存拷贝。虚拟机提供-XX:PretenureSizeThreshold参数,令大于这个值的对象直接分配到老年代。

长期存活对象进入老年代:每个对象对应一个年龄计数器,每在Survivor空间中熬过一次Minor GC,年龄就加1.到达一定年龄是就会晋升到老年代。用参数-XX:MaxTenuringThreshold来设置阈值。

动态对象年龄判定:如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等待到MaxTenuringThreshold中要求的年龄。

参考:- 深入理解Java虚拟机[JVM高级特性与最佳实践](周志明)

 - http://ifeve.com/jvm-yong-generation/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值