java---垃圾回收算法(GC)

目录

一、如何判断一个对象是否存活

1.引用计数法

2.可达性分析法

二、垃圾回收算法

1.标记清除法

2.复制算法

3.标记整理法

4.分代算法

具体流程

注意事项

空间分配担保原则

总结


一、如何判断一个对象是否存活

Java 堆中存放着几乎所有的对象实例,垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象哪些还存活,哪些已经" 死去 " 。判断对象是否已 " " 有如下几种算法:

1.引用计数法

给每一个对象设置一个引用计数器,当有一个地方引用该对象的时候,引用计数器就+1,引用失效时,引用计数器就-1;当引用计数器为0的时候,就说明这个对象没有被引用,也就是垃圾对象,等待回收; 缺点:无法解决循环引用的问题,当A引用B,B也引用A的时候,此时AB对象的引用都不为0,此时也就无法垃圾回收,所以一般主流虚拟机都不采用这个方法。

循环引用问题:

一个类:

Class Person{

 Person p;

一个方法:

void method(){

    Person s = new Person();   //此时该对象的引用数为1

   s.p = s;                                //此时该对象的引用数位2

}                                              //方法结束,引用 s  的生命结束,该对象的引用数位1

//此时该对象还被一个引用指向,但是我们无法拿到这个对象了,因为 引用 s 已经“死”了

2.可达性分析法

从一个被称为GC Roots的对象向下搜索,如果一个对象到GC Roots没有任何引用链相连接时,说明此对象不可用,在java中可以作为GC Roots的对象有以下几种:

1.虚拟机栈中引用的对象

2.方法区类静态属性引用的变量

3.方法区常量池引用的对象

4.本地方法栈JNI引用的对象

可达性图如下所示:

二、垃圾回收算法

1.标记清除法

第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;第二步:在遍历一遍,将所有标记的对象回收掉;特点:效率不行,标记和清除的效率都不高;标记和清除后会产生大量的不连续的空间分片,可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不触发一次GC。

2.复制算法

"复制"算法是为了解决"标记-清理"的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运行高效。复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。

3.标记整理法

标记过程仍与 " 标记 - 清除 " 过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。

4.分代算法

分代算法和上面讲的 3 种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策略,从而实现更好的垃圾回收。这就好比中国的一国两制方针一样,对于不同的情况和地域设置更符合当地的规则,从而实现更好的管理,这就时分代算法的设计思想。
当前 JVM 垃圾收集都采用的是 " 分代收集 (Generational Collection)" 算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java 堆分为新生代和老年代。 在新生代中,每 次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没 有额外空间对它进行分配担保,就必须采用 " 标记 - 清理 " 或者 " 标记 - 整理 " 算法
堆内存划分如图:

具体流程

1.对象优先在Eden分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。

2.在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到 form 分区;

3.Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理,存活的对象会被复制到 to 区;    

4.当后续Eden又发生Minor GC的时候,会对Eden和 to 区进行垃圾回收,存活的对象复制到 from 区,并将Eden 和 to 区清空。

5.部分对象会在 from 和 to 区中来回的复制,如此的交换15次(由JVM参数 Max Tenuring Threshold 决定,默认是15),最终如果还是存活,就存入老年代。

6.Survivor 区内存不足会发生担保分配,超过指定大小的对象可以直接进入老年代(此时如果老年代的内存大小,小于对象的大小,可能会发生一次 Full GC,后面有提到)。

6.老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和老年代

注意事项

新生代:对于一般创建的对象都会进入。

老年代:对于大对象为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率;或者经过N次(一般是默认15次)的垃圾回收依然存活下来的对象;从新生代移动到老年代。

Minor GC、 Major GC、Full GC 的关系: 

1.Minor GC 又称为新生代GC:指的是发生在新生代的垃圾回收。因为Java对象大多都具备朝生夕灭的特性,因此Minor GC(采用的是复制算法)非常频繁,一般回收速度也比较的块。

2.Major GC 又称为老年代GC,一般的老年代GC,总是由于某次Minor GC 引起的,所以 Major GC 发生的时候也是 Full GC,可以看作他两个等效。

空间分配担保原则

如果YougGC时新生代有大量对象存活下来,而 survivor 区放不下了,这时必须转移到老年代中,但这时发现老年代也放不下这些对象了,那怎么处理呢?其实JVM有一个老年代空间分配担保机制来保证对象能够进入老年代。

在执行每次 YoungGC 之前,JVM会先检查老年代最大可用连续空间是否大于新生代所有对象的总大小。因为在极端情况下,可能新生代 YoungGC 后,所有对象都存活下来了,而 survivor 区又放不下,那可能所有对象都要进入老年代了。这个时候如果老年代的可用连续空间是大于新生代所有对象的总大小的,那就可以放心进行 YoungGC。但如果老年代的内存大小是小于新生代对象总大小的,那就有可能老年代空间不够放入新生代所有存活对象,这个时候JVM就会先检查 -XX:HandlePromotionFailure 参数是否允许担保失败,如果允许,就会判断老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小(就是每次从新生代到老年代的对象的平均大小),如果大于,将尝试进行一次YoungGC,尽快这次YoungGC是有风险的。如果小于,或者 -XX:HandlePromotionFailure 参数不允许担保失败,这时就会进行一次 Full GC。

在允许担保失败并尝试进行YoungGC后,可能会出现三种情况:

1.YoungGC后,存活对象小于survivor大小,此时存活对象进入survivor区中。

2. YoungGC后,存活对象大于survivor大小,但是小于老年大可用空间大小,此时直接进入老年代。

3. YoungGC后,存活对象大于survivor大小,也大于老年代可用空间大小,老年代也放不下这些对象了,此时就会发生“Handle Promotion Failure”,就触发了 Full GC。如果 Full GC后,老年代还是没有足够的空间,此时就会发生OOM内存溢出了。


总结

加油偶~~

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值