垃圾回收机制

1 篇文章 0 订阅

垃圾收集又称垃圾回收(Garbage Collection),简称GC

两种判断对象是否存活的算法:

第一种:引用计数算法(Reference Counting)

原理:在对象中添加一个引用计数器,每当一个地方引用时,计数器值就加一;当引用失效时2,计数器值就减一;计数器为0的对象就是不可能再被使用的。
优缺点:原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。但是,在Java领域,至少主流的Java虚拟机里面都没有选用引用算法来管理内存,主要原因是,这个看似简单的算法有很多例外的情况需要考虑,必须要配合大量额外处理才能保证正确地工作,比如单纯的引用计数就很难解决对象之间相互循环引用的问题。
如下:
使用引入算法的缺陷

/**
 * 使用引用算法的缺陷 
 */
public class ReferenceCountingGC {
	public Object instance = null;
	
	private static final int _1MB = 1024 * 1024;
	
	/**
	 * 只用于占点内存,以便在GC日志中看清楚是否有过回收
	 */
	private byte[] bigSize = new byte[2 * _1MB]; 
	
	public static void testGC() {
		ReferenceCountingGC objA = new ReferenceCountingGC();
		ReferenceCountingGC objB = new ReferenceCountingGC();
		objA.instance = objB;
		objB.instance = objA;
		
		objA = null;
		objB = null;
		
	}
			
}

可达性分析算法(Reachability Analysis)

基本原理:通过一系列称为“GC Roots” 的根对象作为起始节点集,从这些节点开始,根据引用关系向下所搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者说不可达时,则证明此对象是不可能再被使用的。

垃圾收集算法

分代收集理论

当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集的理论进行设计,分代收集名为理论,实为一套符合大多数程序进行实际情况的经验法则,它建立在两个分代假说上:

(1)弱分代假说:绝大多数对象都是朝生夕灭的。

(2)强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。
这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(即熬过垃圾收集过程的次数)分配到不同的区域中存储。
       所以显而易见,将朝生夕灭的对象集中到一起,每次回收时只关注少量存活而不是浪费大量性能去标记需要回收的对象,就能以较低的代价获取较多的回收空间;对于年龄较长的对象,将其集中一起,虚拟机就可以用较低的频率去扫描标记要回收的对象。如此设计做到了垃圾收集的性能和空间的兼顾。
       将分代收集理论具体放到现在的商用Java虚拟机里,设计者一般至少会把Java堆划分为新生代和老年代两个区域。顾名思义。在新生代中,每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。

垃圾收集算法
标记-清除(Mark-Sweep)算法

最早出现也是最基础的垃圾收集算法。如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来标记存活的对象。标记过程就是对象是否属于垃圾的判定过程,在上面已讲述。
之所以说它是最基础的收集算法,是因为后续的收集算法大多都是以标记-清除算法为基础,对其确定进行改进而得到的。它的缺点主要有两个:
第一个是执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致效率降低;
第二个是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片。

标记-复制算法

简称为复制算法。为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,1969年Fenichel提出了一种称为“半区复制”(Semispace Copying)的垃圾收集算法,它将可用内存按容量划分为大小相同的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,任何再把已使用的内存空间一次清理掉。这样实现简单,运行高效,只不过其缺点也显而易见,这种复制回收算法的 代价时将可用空间缩小为了原来的一般,空间浪费了太多。
现在的商用Java虚拟机大多都优先采用了这种收集算法去回收新生代。

“Appel式回收”

具体做法:把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,任何直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也即每次新生代可用空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代会被"浪费"掉。当Survivor空间不足以容纳一次MinorGC之后存活的对象时,就需要依赖其他内存区域进行分配担保(Handle Promotion)。HotSpot虚拟机的Serial、ParNew等新生代收集器均采用了这种策略来设计新生代的内存布局。

JVM将堆内存划分为Eden,Survivor,Tenured/Old区域,在Java堆划分出不同的区域之后,垃圾收集器才可以每次只会说其中某一个或者某些部分的区域——因而有了“Minor GC”,“Major GC”,“FullGC”这样的回收类型的划分;
1.新生代(Young/Nursery):
所有新创建的对象都存放在Eden区中,
2.老年代(Old Generation/Tenured):
在年轻代中经历了N次(默认是15)垃圾回收之后还存活的对象,就会被放到年老代中。
3.持久代
 用于存放静态资源,如Java类、方法等。持久代对垃圾回收没有显著影响。

标记-整理算法

标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况。所以老年代一般不能直接选用这种算法。
针对老年代的垃圾收集,1974年Edward Lueders提出了标记-整理算法,首先进行标记,然后让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
标记 - 清除算法与标记 - 整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。移动对象则内存回收时会更复杂,不移动则内存分配时会更复杂,从垃圾收集的停顿时间来看,不移动对象停顿时间会更短,甚至不需要停顿,但是从整个程序的吞吐量来看,移动对象会更划算。关注吞吐量的Parallel Scavenge收集器是基于标记-整理算法的,而关注延迟的CMS收集器则是基于标记-清除算法的。

垃圾回收简要过程:

  1. 新创建的对象,绝大多数都会存储在Eden中
  2. 当Eden满了(达到一定比例)不能创建新对象,就会触发垃圾回收(GC),将无用对象清理掉,然后将剩余对象复制到某个Survivor区中,假定为From,同时清空Eden区
  3. 当Eden区再次满了,会将From中的对象复制到第二个Survivor区中,假定为To,同时清除Eden和From
  4. 重复多次(默认15次)Survivor中没有被清理的对象,则会被复制到老年代(Old区)中
  5. 当Old区满了,则会触发FullGC回收
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值