在之前曾分享了一篇 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 的回收操作。
现在的主流虚拟机都采用分代收集算法,在新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,适合采用复制算法,老年代中存活率高,而且没有额外的空间为它进行分配担保,适合采用标记-清理或标记-整理算法。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有