1 概述

在前一篇文章中讲到了Java虚拟机的基础知识和运行时数据区的划分,在运行时数据区的划分中,可分为线程共享区域和线程私有区域,而Java的垃圾回收就发生在线程共享区域中,更直观的说法就是Java的垃圾回收大部分都发生在Java的堆(Heap)区域内。

1.1 哪些对象需要回收

在了解了Java垃圾回收主要发生在哪些区域之后,然后我们就需要知道在Java中哪些对象是需要被当做垃圾回收的,这是一个很简单的问题,既然是需要回收的,那肯定就是在程序中已经使用过且后续不会再用到的对象,那怎么判断一个对象是否还会被用到呢?答案就是引用。

假设一个对象A,在程序中被其他对象所引用,那么对象A就不能被回收,如果对象A在程序中没有被任何其他对象引用,那么对象A就会在发生垃圾回收的时候被回收掉。

1.1.1 引用计数算法

那么问题又来了,在Java中如何判断一个对象是否被引用呢?其实最简单的一个算法就是引用计数法。引用计数法的原理也很简单,在每一个对象中添加一个引用计数器,每当有其他地方引用到当前对象时,计数器的值就加1;当引用失效时,计数器的值就减1,这样在垃圾回收的时候就直接判断当前对象的引用计数器的值,如果值为0,就表示当前对象已经没有任何地方引用,可以回收,反之若计数器的值不为0,则表示当前对象还被其他地方引用,不回收。

但是引用计数法有一个最大的缺陷,就是循环引用问题,见下图:

如图所示,有两个对象ObjectA和ObjectB,ObjectA对象引用ObjectB对象,ObjectB对象引用ObjectA对象,两个对象之间形成了一个相互引用的关系,所以两个对象的计数器值都是1,即使这两个对象在程序中已经没有被用到了,但是垃圾回收的时候还是不会去回收这两个对象。

这还只是两个对象的相互引用,如果是成百上千个不使用的对象之间相互引用形成一个循环引用,那么对Java堆空间会造成极大的浪费。

因此,在Java垃圾回收中并没有使用引用计数法,使用的而是另外一种算法:可达性分析算法。

1.1.2 可达性分析算法

可达性分析算法的思路就是通过一系列GC Roots作为根对象,这些GC Roots根对象都是不会被垃圾回收的一些对象,然后根据这些GC Roots的引用关系向下搜索,在搜索过程中所走过的路径就是“引用链”,如果一个对象从GC Roots开始无法通过引用链搜索到的话,那么这个对象就是不可达的,则表示这个对象在程序中不再被使用,可以被回收。

如上图所示,由多个GC Roots根对象可组成一个GC Root Set集合,只要是从GC Roots根对象通过引用链能够达到的对象就是在使用的对象,而图中的Object5,Object6,Object7三个对象之间虽然相互有引用,但是它们到GC Roots是不可达的,因此这三个对象会被判定为可回收对象。

在Java中,可以被作为GC Roots的对象有以下这些:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常亮引用到的对象。
  • 本地方法栈中引用到的对象(也就是Native中引用到的对象)。
  • Java虚拟机的内部引用,如基本数据类型对应的Class对象,异常对象,系统类加载器。
  • synchronized持有的对象。
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

2 垃圾回收算法

在了解完垃圾回收主要发生的区域以及如何判断对象是否可被回收之后,再需要了解的就是垃圾回收算法,垃圾回收算法就会使用到以上提到的可达性分析算法标记对象是否可被回收。

2.1 标记清除算法

标记清除算法是最早出现并且也是最简单的一种垃圾回收算法,在整个垃圾回收过程中总共分为标记和清除两个步骤:

  1. 标记需要回收的对象。
  2. 将第一步标记的对象进行统一的回收。

上图就是标记清除算法的两个步骤,使用标记清除算法虽然简单,但是标记清除算法存在两个缺点:

  1. 执行效率不稳定,如果Java堆中包含有大量的需要回收的对象,那么使用该回收算法的时候就需要去标记大量的对象,然后再去对标记的对象进行回收,导致该回收算法的效率会随着对象数量的增长而降低。
  2. 内存碎片化问题,通过上图可以看到,当完成对象回收之后,未使用的内存区域呈现无规律的随机分布,这就造成了内存碎片化的问题,如果当前Java程序中需要实例化一个大对象,但是由于内存碎片化的问题导致无法分配一个连续的大内存空间,就会导致提前又触发一次垃圾回收。

标记清除算法常被使用在老年代中,因为老年代中的对象大部分都不会被清理掉,只存在于少部分的对象会被清理,所以在老年代中不会存在标记大量对象并清除的情况。

2.2 标记复制算法

针对于标记清除算法存在的问题,在标记清除算法的基础之上,又提出了一种算法:标记复制算法,标记复制算法中将内存区域划分为两块大小一致的区域,每次只使用其中一块,当这一块的内存用完了之后就将还存活的对象复制到另一块区域上去,然后将当前块的内存空间一次清理掉,假设内存分为A和B两块,首先使用A内存,B作为保留,则标记复制算法步骤如下:

  1. 在A中标记需要回收的对象
  2. 将A中存活的对象复制到B中
  3. 将A的内存空间一次全部清理掉

上图是标记复制算法的回收步骤,该算法同样存在以下问题:

  1. 复制回收算法将原有的内存区域划分为两块,且每次只使用其中一块,可使用内存相比标记清除算法缩小了一半,对空间浪费太大。
  2. 如果内存中存活的对象太多,在复制的过程中复制大量的存活对象会造成效率低下,额外开销大,但如果内存中存活的对象为极少数,那么复制算法无疑是很好的回收算法。

标记复制算法适用于Java堆中的新生代部分的垃圾回收,因为在新生代区域中,正常情况下能够存活下来的对象只有极少数(2%或3%?),这样复制算法就不存在大量复制对象的情况,针对于空间浪费问题,将新生代区域划分为一块较大的Eden区域和两块较小的Survivor区域,每次分配对象时使用Eden和其中一块Survivor,当发生垃圾回收时,将Eden中和Survivor中存活的对象复制到另一块Survivor中,然后将Eden和已使用过的Survivor内存空间直接清理掉,下一轮分配对象时使用Eden和之前保存了存活对象的那一块Survivor,依次循环。

关于新生代划分可看上一篇文章:JVM入门

HotSpot虚拟机默认的Eden和Survivor的大小比例为8:1,Eden占新生代80%,两个Survivor各占10%,这样每次可使用的内存空间就是90%,比标记复制算法默认的等分50%要多得多。

逃生门安全设计:当Survivor空间不足以容纳一次垃圾回收之后存活的对象时,就需要依赖于其他区域(老年代)进行分配担保。

2.3 标记整理算法

在老年代中大部分对象都会存活下来,所以不适合标记复制算法,而且使用标记清除算法又会造成大量内存碎片,因此针对于老年代对象的死亡特征,又提出了一种回收算法:标记整理算法,标记整理算法的标记步骤和标记清除算法一致,但是在标记之后不会直接进行清除,而是首先将存活的对象向内存空间的一端进行移动,将所有存活的对象都连续的放在一起,然后直接清理掉边界以外的内存,标记整理算法步骤如下:

  1. 标记需要回收的对象
  2. 将存活的对象向内存空间的一端移动
  3. 直接清理掉边界以外的内存

在老年代中常使用的垃圾回收算法是标记清除算法和标记整理算法。

如果使用标记整理算法,在老年代区域中每次回收都有大量的对象存活,并且移动对象时需要更新所有对象的引用,所以会造成比较大的系统开销,而且对象移动操作必须全程暂停用户应用程序(Stop The Word)才能进行。

如果使用标记清除算法,则会造成内存碎片。

基于以上两种垃圾回收方式,各自都有优点和缺点,所以不同的垃圾收集器在老年代中所采用的垃圾回收算法也是有区别的,例如Parallel Scavenge收集器采用的就是标记整理算法,而CMS收集器则采用的是标记清除算法。

还有就是两种算法配合使用,首先采用标记清除算法,当内存碎片达到一定程度的时候使用标记整理算法回收一次,整理一下内存碎片,然后再又继续使用标记清除算法,前面提到的CMS收集器就是首先采用标记清除算法,当内存碎片过多然后采用标记整理算法。

关于垃圾收集器下一篇文章详解。

参考资料:《深入理解Java虚拟机 第三版》 周志明

Java垃圾回收算法详解相关推荐

  1. JVM之垃圾回收算法详解

    JVM之垃圾回收算法详解 现有的垃圾回收算法 分类 垃圾收集器的设计原则 标记-清除算法 缺点 标记-复制算法 "Apple回收策略" 缺点 标记-整理算法 缺点 总结 现有的垃圾 ...

  2. JVM底层原理+四大垃圾回收算法详解-周阳老师

    转载自,感谢原作者:https://www.jianshu.com/p/9e6841a895b4 注意:垃圾回收算法周阳老师讲的有错误,具体在p19,四大垃圾回收算法为复制算法.标记-整理算法.标记- ...

  3. jvm垃圾回收算法详解

    前言 相比C语言,JVM虚拟机一个优势体现在对对象的垃圾回收上,JVM有一套完整的垃圾回收算法,可以对程序运行时产生的垃圾对象进行及时的回收,以便释放JVM相应区域的内存空间,确保程序稳定高效的运行, ...

  4. 【JVM基础】垃圾回收算法详解(引用计数、标记、清除、压缩、复制)

    前言 笔记参考 Java 全栈知识体系.星羽恒.星空茶 文章目录 前言 垃圾回收概述 引用计数法 案例 优点 缺点 标记.清除.压缩 标记 清除 压缩 标记清除算法 优点 缺点 标记压缩算法 优点 缺 ...

  5. go语言垃圾回收机制详解

    Golang GC 垃圾回收机制详解 独一无二的小个性 https://blog.csdn.net/u010649766/article/details/80582153 摘要 在实际使用 go 语言 ...

  6. python内存的回收机制_python的内存管理和垃圾回收机制详解

    简单来说python的内存管理机制有三种 1)引用计数 2)垃圾回收 3)内存池 接下来我们来详细讲解这三种管理机制 1,引用计数: 引用计数是一种非常高效的内存管理手段,当一个pyhton对象被引用 ...

  7. 第十五章: 菱悦 -垃圾回收GC详解

    第 15章 垃圾回收GC详解 文章目录 第 15章 垃圾回收GC详解 1.System.gc() 的理解 1.1.System.gc() 方法 1.2.不可达对象回收行为 2.内存溢出与内存泄漏 2. ...

  8. go java 垃圾回收_Go/Java垃圾回收算法对比解析

    原标题:Go/Java垃圾回收算法对比解析 导读:GC 是大部分现代语言内置的特性,本文作者针对 Go 语言声称的 10ms 以下的 GC 停顿进行了深入分析,还同 Java 的垃圾收集器做了对比.G ...

  9. incompatible jvm_JVM垃圾回收回收算法详解

    一分钟的深入思考抵得过一小时的盲目寻找 根据对Java对象生命周期的统计,大部分对象只存活一小段时间,存活下来的对象能存活很长时间.Java虚拟机分代回收的思想,也就是从这个统计进行设计的.分代设计就 ...

最新文章

  1. css海浪动画代码,不行一行代码,纯css实现海浪动态效果!
  2. 病毒周报(071029至071104)
  3. 脑电分析系列[MNE-Python-13]| bad通道介绍
  4. android 网络图片查看器,Handler的用法
  5. 党在心中(turtle画图)
  6. 安装php时,make步骤报错make: *** [sapi/fpm/php-fpm] Error 1
  7. teamlab与redmine试用对比报告
  8. Windows应急响应操作手册
  9. 微信微擎口红机带闯三关送礼品完整源码+精美UI支持海报推广
  10. 论文解读丨空洞卷积框架搜索
  11. 【Python】Python3.7.3 - 虚拟环境:pyvenv过时;使用python -m venv命令
  12. 拓端tecdat|“新媒体”和“社群”调查报告
  13. command对象提供的3个execute方法是_【面试题】面向对象编程篇-01
  14. 神雕侠侣服务器维修,《神雕侠侣》2019年6月13日更新维护新服开启公告
  15. 01. Introdunction to Zero Knowlege -- Alon Rosen[零知识介绍]
  16. ATFX:美联储会议纪要发布后,美元指数逼近105关口
  17. 奇迹之剑萌新晋升大神辅助攻略 奇迹之剑游戏脚本挂机工具介绍
  18. 火狐浏览器设置关闭提醒
  19. php composer.phar install,解决composer.phar安装问题
  20. 计算机视觉在农业领域中的应用

热门文章

  1. 你应该拿哪一份饭呢?
  2. mysql存储过程自动生成周次数据
  3. java atomiclong 使用_Java AtomicLong set()用法及代码示例
  4. vs2010更换主题教程
  5. 【VUE项目实战】41、添加商品分类功能(二)
  6. 计算机专业培养目标,计算机专业培养目标
  7. 史上最强理论 孙悟空
  8. 树莓派系统汉化教程(汉语+中文字体库+中文输入法pinyin(拼音))
  9. matlab中Nurbs库的简单使用
  10. Android Studio出现错误 Error:Unable to start the daemon process