前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 垃圾收集器的垃圾收集算法

Java 垃圾收集器的垃圾收集算法

作者头像
CS实验室
发布2021-03-22 12:26:18
4840
发布2021-03-22 12:26:18
举报
文章被收录于专栏:CS实验室

垃圾收集器的垃圾收集算法

在之前曾分享了一篇 Junnplus 关于 Python 垃圾回收的文章,孟同学读后不服,立马撰文以表达对 Java 真挚的爱❤️。 各个语言的 GC 算法其实大同小异而且抄来抄去,对比着看也是一件有趣的事情。

在 Java 中,垃圾回收是个基础而有趣的话题,本文主要讲解 Java 垃圾收集器的垃圾收集算法,首先,需要理解几个概念:

引用计算法:通俗的讲,引用计数法是这样这样一种场景,在类中设置一个计数变量,专门用来存储当前类有多少引用,如果被某个类引用,变量 +1,如果不再引用,变量就 -1,变量为 0 的时候认为此类没有任何引用。这种方法会有一个缺陷:如果两个类互相引用,两个类的计数变量都不为 0,但是两个类应该被回收,因为计数变量大于 0,所以无法回收。三个对象 A,B,C,A 中引用了 B,B 中引用了 C,C 中引用了 A,这就成为了一个循环引用,四个对象,五个甚至更多,都可以构成循环引用,其实两个类也可以看作循环引用。

可达性:通俗来说,可达性分析就是从一个点是否能搜索到另一个点,例如中国版图,以水为界,以陆地为连接,北京为根节点,从北京出发,在陆地上能到达上海,新疆,西藏,但是海南和台湾,因为隔着琼州海峡和台湾海峡,与大陆不连通,这种场景被认为不可达(其实坐船,坐飞机都能到)。从根节点查找其他节点的过程可以理解为可达性分析,如果某个或某几个对象连通,但是没有和根节点连通,我们认为这个(些)对象不可达,这样能解决循环引用问题,根节点的话,问度娘吧。

标记清理算法

此算法就是字面上的意思,先是把内存中需要收集的对象标记下来,然后进行内存空间回收。

标记的方法可以使用可达性分析,不采用引用计数法。优点是算法简单,易于理解,缺点是标记和清理的效率都很低,而且这种方法会产生内存碎片。假设经过了一次内存回收,空间中剩余一共 10k 内存,最大的块能容纳6k大的对象,现在要申请 7k 内存,发现在内存中找不到能容下 7k 的块,于是要提前触发一次垃圾回收,不仅仅是浪费资源,效率也大大降低。

复制算法

为了解决效率问题,有人提出了复制算法:把内存空间分成相等的两份(内存大小比例为 1:1 ),暂且记作 A、B,用其中的一份用来内存分配,这里我们选 A,当触发回收操作的时候,把不回收的对象拷贝到另一块内存中,也就是把 A 中的拷贝到 B 中去,然后把 A 全部回收,这样,用来分配内存的那一块就变成了 B,B 中还有存活的对象,A 中被全部清空,下一次回收时,再把 B 中存活的对象拷贝到 A 中,然后全部回收 B。复制算法解决了效率低下的问题和内存碎片过多问题,但是把内存等分为 2 份,对内存实在是太浪费资源了。假设你买了台新笔记本,内存是 16G,但是只能用 8G,是不是很不爽?

分代思想(这不是算法): 根据一份IBM的专门调查报告,发现 98% 的对象都是“朝生夕死”,存活周期相当短(想想我们的方法中的变量),还有一些对象生命周期比较长,人们把存活周期比较短的分到新生代,把存活周期比较长的分到老年代。

标记整理

复制算法有很多缺点:当存活的对象较多,复制会花费很多的时间,效率低下,最致命的是会有50% 的空间不能使用。极端情况下,如果应该被回收的内存中对象 100% 存活,然后又有新的内存申请,我们不得不增加一块额外的空间来应对这种极端情况。老年代中的对象存活周期都比较长,有人在标记-清理算法的基础上,提出了标记-整理算法,这种算法先去标记需要回收的对象(这个和标记-清理算法的标记过程是一样的),然后将存活的对象往空间的一端移动,记录需要回收的对象和存活对象的界限,然后清理需要回收的内存区域。

分代收集

这并不是新的算法,而是根据新生代和老年代不同的存活周期,选择不同的算法,老年代采用标记-整理算法,而新生代采用复制算法,不过比例不是 1:1,而是 8:1:1,占 8/10 区域的是新生代,被称作 Eden,另外两块区域大小相同,被称作 Survivor1 和 Survivor2,内存回收时,把 Eden 和 Survivor1 中的存活对象拷贝到 Survivor2 中,回收 Eden 和 Survivor1 两个空间,下一次回收时,把 Eden 和 Survivor2 中的存活对象拷贝到 Survivor1 中,回收 Eden 和 Survivor2 两个空间,如此往复回收。因为 Eden 空间大,并且其中的对象会被回收 90% 以上,所以复制算法比较有优势。如果出现 Eden 和 Survivor(记为 Survivor) 中存活的对象比另一个 Survivor(记为 Survivor2) 空间大的情况,就会选择 Survivor1 中的经过多次回收还在存活的对象(这种对象被认为存活周期较长)进入老年代,释放出足够的空间,这种操作被称为分配担保,就像是银行贷款的担保人一样。需要存放存活对象的 Survivor 中内存空足够了,就进行 Eden 和 Survivor 的回收操作。

现在的主流虚拟机都采用分代收集算法,在新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,适合采用复制算法,老年代中存活率高,而且没有额外的空间为它进行分配担保,适合采用标记-清理或标记-整理算法。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CS实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 垃圾收集器的垃圾收集算法
    • 标记清理算法
      • 复制算法
        • 标记整理
          • 分代收集
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档