java中的垃圾回收算法(GC)

什么是垃圾回收算法

java程序运行的整个过程,会自动对相关内存的进行释放、回收,防止出现内存泄漏,这也是java语言一个显著的特点。那么哪些内存需要回收的呢,、什么时候需要回收、如何回收,这就要用到判断对象是否存活的算法,常用的有引用计数法、可达性分析算法,而java使用的垃圾收集算法是 标记-清除算法、复制算法(Copying)、标记-整理算法(Mark-compact)、分代收集算法。

引入计数法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1;任何时刻计数器为0的对象就是不可能再被使用的,是可以回收的对象。
优点:
引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点:
无法检测出循环引用。*如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

引用计数算法无法解决循环引用问题,例如:

public class Test{
		public Object instance=null;
		public statc void testGC(){
			Test A=new Test();
			Test B=new Test();
			A.instance=B;
			B.instance=A;
			A=null;
			B=null;
			System.gc();
		}
}

对象A和对象B都有字段 instance 赋值使A.instance=B ,B.instance=A,除此之外,这2个对象再无任何引用,实际上这2个对象已经不可能再被访问,但是它们因为相互引用着对方,导致它们的引用计数都不为0,则无法回收。

可达性分析算法

在这里插入图片描述
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”,对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的,要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收

标志-清除算法

分为2个阶段,标志和清除,标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。如图
在这里插入图片描述
该算法不足之处在于:效率不高,并且标志清除之后会产生大量不连续的内存碎片,控件碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

复制算法

为了解决效率问题,将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块用完了,就将还存活着的对象赋值到另外一块上面,然后在把已使用过的内存空间一次清理掉。如图
在这里插入图片描述
这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原
本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。

标记整理算法

标记过程与标记-清除算法一样,后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存,标记-整理 算法示意图 如下

在这里插入图片描述

分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代,这样可以根据各个年代的特点采用最适当的收集算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理 或者 标记-整理算法来进行回收。

补充一个知识点
Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年
如图
在这里插入图片描述
新生代:是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。
1.Eden区:java对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
2.ServicorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。
3.ServivorTo:保留了一次MinorGC过程中的幸存者。
4.MinorGC的过程(复制->清空->互换):首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区),然后,清空 Eden 和 ServicorFrom 中的对象;最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom
区。

老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。

新生代内存回收采取复制算法
新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照 1:1 来划分新生代。一般将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。如图
在这里插入图片描述
老年代内存回收采取标记-整理算法

  1. JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储 class 类常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。
  2. 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 前存放对象的那一块),少数情况会直接分配到老年代。
  3. 当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,Eden Space 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From Space 进行清理。
  4. 如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。
  5. 在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。6. 当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被移到老年代中。

永久代标记-整理算法进行垃圾回收
java虚拟机内存中的方法区在Sun HotSpot虚拟机中被称为永久代,是被各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。永久代垃圾回收比 较少,效率也比较低,但是也必须进行垃圾回收,否则会永久代内存不够用时仍然会抛出OutOfMemoryError异常。

总结:对java的大致垃圾回收算法了解的就如上所说,可能还有更深入的等待去发掘,能力有限,在看了一些博客文章,总结到此,为以后回头翻看看也可以更方便。

参考如下:
[1]: https://www.cnblogs.com/huajiezh/p/5769255.html
[2]: 《JAVA核心知识点整理》
[3]: 《深入理解Java虚拟机JVM高级特性与最佳实践》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值