当前位置:   article > 正文

【面试精讲】Java垃圾回收算法分析和代码示例_java垃圾回收的算法

java垃圾回收的算法

【面试精讲】Java垃圾回收算法分析和代码示例

目录

一、引用计数(Reference Counting)算法

二、可达性分析(Reachability Analysis)算法

三、标记-清除(Mark-Sweep)算法

四、复制(Copying)算法

五、标记-整理(Mark-Compact)算法

六、分代收集(Generational Collection)算法

七、死亡对象判断

总结

 博主v:XiaoMing_Java


在深入探讨Java垃圾回收(GC)算法之前,了解Java内存管理的基本原理是至关重要的。Java虚拟机(JVM)通过垃圾回收过程自动管理内存,确保不再使用的对象能够被及时清理,从而避免内存泄漏。本文将详细介绍Java中常见的几种垃圾回收算法,并探讨它们的适用场景、优缺点以及工作原理。

一、引用计数(Reference Counting)算法

它是指在创建对象时关联一个与之相对应的计数器,当此对象被使用时加 1,相反销毁时 -1。当此计数器为 0 时,则表示此对象未使用,可以被垃圾收集器回收。

引用计数算法的优缺点很明显,其优点是垃圾回收比较及时,实时性比较高,只要对象计数器为 0,则可以直接进行回收操作;而缺点是无法解决循环引用的问题,比如以下代码:

  1. class CustomOne {
  2.     private CustomTwo two;
  3.     public CustomTwo getCustomTwo() {
  4.         return two;
  5.     }
  6.     public void setCustomTwo(CustomTwo two) {
  7.         this.two = two;
  8.     }
  9. }
  10. class CustomTwo {
  11.     private CustomOne one;
  12.     public CustomOne getCustomOne() {
  13.         return one;
  14.     }
  15.     public void setCustomOne(CustomOne one) {
  16.         this.one = one;
  17.     }
  18. }
  19. public class RefCountingTest {
  20.     public static void main(String[] args) {
  21.         CustomOne one = new CustomOne();
  22.         CustomTwo two = new CustomTwo();
  23.         one.setCustomTwo(two);
  24.         two.setCustomOne(one);
  25.         one = null;
  26.         two = null;
  27. }
  28. }

二、可达性分析(Reachability Analysis)算法

 是目前商业系统中所采用的判断对象死亡的常用算法,它是指从对象的起点(GC Roots)开始向下搜索,如果对象到 GC Roots 没有任何引用链相连时,也就是说此对象到 GC Roots 不可达时,则表示此对象可以被垃圾回收器所回收,如下图所示:

 

三、标记-清除(Mark-Sweep)算法

工作原理:标记-清除算法是最基础的垃圾回收策略,其过程分为两个阶段:标记清除。在标记阶段,算法遍历所有可达对象,并对这些对象进行标记;随后,在清除阶段,未被标记的对象,即不可达对象,将被视为垃圾并被回收。

优点:实现相对简单,不需要移动对象。

缺点:两大主要缺点是执行效率不高(特别是在堆内存大时)和造成内存碎片。

代码示例

由于JVM底层实现垃圾回收,我们平时编写的Java代码一般无需直接与垃圾回收算法打交道。因此,以下展示的并非真正执行垃圾回收的代码,而是为了帮助理解标记-清除算法的简化伪代码:

  1. // 伪代码,用于说明标记-清除算法
  2. void mark(Object root) {
  3. if (root == null || root.isMarked()) return;
  4. root.mark(); // 标记当前对象
  5. for (Object ref : root.getReferences()) {
  6. mark(ref); // 递归标记引用对象
  7. }
  8. }
  9. void sweep() {
  10. for (Object obj : heap) {
  11. if (!obj.isMarked()) {
  12. heap.remove(obj); // 清除未标记的对象
  13. } else {
  14. obj.unmark(); // 清除标记,为下次GC做准备
  15. }
  16. }
  17. }

四、复制(Copying)算法

工作原理:复制算法将内存空间一分为二,每次只使用其中的一个子空间。在垃圾回收时,将正在使用的内存区域中的活动对象复制到未使用的区域中,然后清除正在使用的内存区域中的所有对象,达到快速回收的目的。

优点:没有内存碎片问题,且可以快速完成内存回收。

缺点:内存利用率低,因为任何时候只有一半的内存区域可以被利用。

代码示例 

  1. // 伪代码,用于说明复制算法
  2. void copy() {
  3. for (Object obj : currentArea) {
  4. if (obj.isAlive()) {
  5. moveTo(obj, anotherArea); // 将活动对象复制到另一个区域
  6. }
  7. }
  8. clear(currentArea); // 清理当前区域
  9. swapAreas(); // 交换当前区域和另一个区域的角色
  10. }

五、标记-整理(Mark-Compact)算法

工作原理:标记-整理算法是标记-清除算法的改进版本。在完成标记活动对象之后,不是简单地清理未标记对象,而是将所有活动对象都向一端移动,然后直接清理边界外的内存。

优点:解决了标记-清除算法的内存碎片问题。

缺点:需要移动对象,增加了额外的开销。

代码示例 

  1. // 伪代码,用于说明标记-整理算法
  2. void markCompact() {
  3. markAll(); // 标记所有活动对象
  4. compact(); // 将所有活动对象移动到内存区域的一端
  5. clearRest(); // 清理掉边界外的内存
  6. }

六、分代收集(Generational Collection)算法

工作原理:分代收集算法基于这样一个事实:不同生命周期的对象应当采取不同的收集策略。Java堆分为新生代和老年代,新生代使用复制算法,老年代使用标记-整理或标记-清除算法。

优点:提高了垃圾回收的效率,尤其是对于那些生命周期短的对象。

缺点:实现复杂,需要更细致地管理内存区域。

代码示例 

  1. // 伪代码,用于说明分代收集算法
  2. void generationalCollect() {
  3. if (minorGCCondition()) {
  4. minorGC(); // 在新生代执行复制算法或其他适合短生命周期对象的算法
  5. }
  6. if (majorGCCondition()) {
  7. majorGC(); // 在老年代执行标记-清除或标记-整理算法
  8. }
  9. }

七、死亡对象判断

 当使用可达性分析判断一个对象不可达时,并不会直接标识这个对象为死亡状态,而是先将它标记为“待死亡”状态再进行一次校验。校验的内容就是此对象是否重写了 finalize() 方法,如果该对象重写了 finalize() 方法,那么这个对象将会被存入到 F-Queue 队列中,等待 JVM 的 Finalizer 线程去执行重写的 finalize() 方法,在这个方法中如果此对象将自己赋值给某个类变量时,则表示此对象已经被引用了。因此不能被标识为死亡状态,其他情况则会被标识为死亡状态。

  1. public class FinalizeTest {
  2.     // 需要状态判断的对象
  3.     public static FinalizeTest Hook = null;
  4.     @Override
  5.     protected void finalize() throws Throwable {
  6.         super.finalize();
  7.         System.out.println("执行了 finalize 方法");
  8.         FinalizeTest.Hook = this;
  9.     }
  10.     public static void main(String[] args) throws InterruptedException {
  11.         Hook = new FinalizeTest();
  12.         // 卸载对象,第一次执行 finalize()
  13.         Hook = null;
  14.         System.gc();
  15.         Thread.sleep(500); // 等待 finalize() 执行
  16.         if (Hook != null) {
  17.             System.out.println("存活状态");
  18.         } else {
  19.             System.out.println("死亡状态");
  20.         }
  21.         // 卸载对象,与上一次代码完全相同
  22.         Hook = null;
  23.         System.gc();
  24.         Thread.sleep(500); // 等待 finalize() 执行
  25.         if (Hook != null) {
  26.             System.out.println("存活状态");
  27.         } else {
  28.             System.out.println("死亡状态");
  29.         }
  30.     }
  31. }

总结

垃圾回收是Java内存管理中不可或缺的一部分,不同的垃圾回收算法各有优劣,适用于不同的场景和需求。现代JVM如HotSpot,提供了多种垃圾回收器,比如Serial、Parallel、CMS、G1等,它们背后的实现原理基本上是上述垃圾回收算法的变体或组合。理解这些算法的基本原理,可以帮助开发者更好地理解JVM的内存管理机制,从而写出更高效、更稳定的Java应用程序。

如果本文对你有帮助 欢迎 关注 、点赞 、收藏 、评论, 博主才有动力持续记录遇到的问题!!!

 博主v:XiaoMing_Java

  声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】

推荐阅读
相关标签