垃圾回收机制

一、垃圾回收机制
(1)为什么要有垃圾回收?
学过C/C++都知道每一次new出一个对象,在不使用时都要调用对应方法将其释放。程序每次开辟一块内存空间,在使用完毕后也就需要将其释放掉,如果不手动释放,这块内存的空间就会持续存在,一直到进程结束(堆上的内存生命周期长,不像栈会随着方法执行结束。栈帧销毁而自动释放,堆则默认不能自动释放),这样会导致一个严重问题:内存泄漏。如果内存一直占着不用,又不释放,就会导致剩余空间越来越少,进一步导致后面的内存申请操作失败。而Java中为了防止程序员在开辟空间时忘记将其释放掉,保证程序员每次开辟空间在使用完毕后能将其释放,引入了GC垃圾回收机制,也就不需要程序员手动去释放。
(2)GC实际工作过程
GC主要是针对堆上的内存区域进行释放的,且GC是以"对象"为基本单位进行回收的,而不是字节。
其具体工作过程:
a)找到垃圾/判定垃圾
那个对象是垃圾,哪个不是?哪个对象以后一定不用了,哪个对象后面还可能使用?判断思路是,看这个对象有没有引用指向它。具体有两种实现方式:
(1)引用计数(引用计数不是java的做法,是python/php的)
给每个对象分配了一个计数器,每次创建一个引用指向该对象,计数器+1,每次该引用被销毁了,计数器就-1。

{
    Test t =  new Test(); //Test对象的引用技术为1
    Test t2 = t; //t2也指向t,引用计数为2
    Test t3 = t; //t3也指向t,引用计数为3
}
//大括号结束,上述三个引用超出作用域失效,此时引用计数就是0了,此时new Test()对象就是垃圾了

此方法简单有效,但是Java没有使用,原因:
①内存空间浪费的多,导致利用率低。
②存在循环利用的问题

class{
   Test t = null;
}
Test a = new Test(); //1号对象,引用计数是1
Test b = new Test(); //2号对象,引用计数是1
a.t = b;  //a.t指向2号对象,2号对象引用计数是2
b.t = a;  //b.t指向1号对象,1号对象引用计数是2

接下来,如果a,b引用同时销毁,此时1号对象和2号对象引用计数都-1,但-1之后结果还是1,不是0。虽然不是0,不能释放内存,但是实际上这两对象已经没有办法被访问到了。
(2)可达性分析
Java中的对象,都是通过引用来指向并访问的,通常表现为一个引用指向一个对象,这个对象里的成员又指向别的对象。整个Java中所有的对象,就通过类似于上述的关系 链式/树形结构整体给串起来。
可达性分析是Java的做法,把所有对象被组织起来的结构视为是树,从根结点出发,遍历树所有能被访问的对象,标记为"可达"(不能被访问到,就是不可达),JVM拿着一个所有对象的名单,通过上述遍历,把可达的标记出来,剩下不可达的就可以作为垃圾进行回收了。

class Node{
  public int val;
  public Node left;
  public Node right;
}
public class Test{
  public static Node built(){
     Node a = new Node();
     Node b = new Node();
     Node c = new Node();
     Node d = new Node();
     Node e = new Node();
     Node f = new Node();
     Node g = new Node();
     a.val = 1;
     b.val = 1;
     c.val = 1;
     d.val = 1;
     e.val = 1;
     f.val = 1;
     g.val = 1;
     a.left = b;
     a.right = c;
     b.left = d;
     b.right = e;
     e.left = g;
     c.right = f;
     return a;
  }
  public static void main(String[] args){
     Node root = built();
}

b)如何清理垃圾
(1)标记清除
该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。
在这里插入图片描述
这种垃圾收集算法会带来两个明显的问题:
①效率问题
②空间问题(标记清除后会产生大量不连续的碎片);
(2)标记-复制算法
为了解决效率问题,“标记-复制”算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存空间的一半进行回收。
在这里插入图片描述
这种垃圾收集算法会带来两个明显的问题:
①空间利用率低
②如果要是垃圾少,有用对象多,复制成本就比较多了
(3)标记整理
标记复制算法在对象存活率较高时会进行比较多的复制操作,效率会变低,因此在老年代一般不能使用复制算法。根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
在这里插入图片描述
4)分代算法(复合策略)
分代算法和上面讲的 3 种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策略,从而实现更好的垃圾回收。
当前 JVM 垃圾收集都采用的是"分代算法",这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。
在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。
c)垃圾回收器
上面的垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
垃圾收集器的作用:垃圾收集器是为了保证程序能够正常、持久运行的一种技术,它是将程序中不用的死亡对象也就是垃圾对象进行清除,从而保证了新对象能够正常申请到内存空间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值