700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 【JVM基础】垃圾回收算法详解(引用计数 标记 清除 压缩 复制)

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

时间:2021-10-05 01:04:09

相关推荐

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

前言

笔记参考

Java 全栈知识体系、星羽恒、星空茶

文章目录

前言垃圾回收概述引用计数法案例优点缺点标记、清除、压缩标记清除压缩标记清除算法优点缺点标记压缩算法优点缺点复制算法步骤优点缺点总结

垃圾回收概述

JVM是具有垃圾回收机制的,与c/c++不同,Java程序员不需要在写程序的时候考虑垃圾回收的问题,只需要专注代码逻辑即可,一定程度上减轻了程序员的负担。在JVM中,垃圾回收主要发生的地方是在堆内存中,因为在JVM的栈内存中,每个栈帧所需的内存大小是在编译期间就已经确定好了的,因此这个区域的内存分配和回收都是具有确定性的,也就不需要考虑过多的回收的问题,方法结束后,栈帧也就出栈了,内存也就跟着回收了。

而在堆内存中,我们只有在程序运行期间才会知道创建了哪些对象,这部分内存的分配和回收都是动态的,所以需要垃圾收集器关注这一块内存区域。

引用计数法

引用计数法是一种历史悠久的算法,最早是在1960年George E. Collins首次提出的,该算法的实现是假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败时,对象A的引用计数器就-1,如果对象A的计数器的值为0,就说明对象A没有引用了,可以被回收。但是在目前主流的Java虚拟机中并没有使用引用计数法,其中主要原因也是因为引用计数法无法回收循环引用的变量

案例

public class LexGC {public static void main(String[] args) {TestA a = new TestA();TestB b = new TestB();a.b=b;b.a=a;a = null;b = null;}}class TestA{public TestB b;}class TestB{public TestA a;}

虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不会被回收。

优点

实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember 错误区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象

缺点

每次对象被引用时,都需要去更新计数器,有一点时间开销浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计无法解决循环引用问题

标记、清除、压缩

标记

垃圾回收的第一步是标记。垃圾回收器此时会找出内存哪些在使用中,哪些不是。

上图中,蓝色表示已引用对象,橙色表示未引用对象。垃圾回收器要检查完所有的对象,才能知道哪些有被引用,哪些没。如果系统里所有的对象都要检查,那这一步可能会相当耗时间。

清除

垃圾回收的第二步是清除,这一步会删掉标记出的未引用对象。

压缩

垃圾回收的第三步是压缩,为了提升性能,删除了未引用对象后,还可以将剩下的已引用对象放在一起(压缩),这样就能更简单快捷地分配新对象了。

标记清除算法

在介绍标记清除算法之前,这里要先提一下可达性分析算法,所谓可达性分析就是用来判断对象是否存活,这个算法的基本思路就是以“GC Roots”(在Java中,虚拟机栈中的引用对象,方法区中静态属性引用对象,方法区中常量引用对象,本地方法栈中的JNI(Java native interface)都可以最为GC Roots)为起始点,从这个节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链时,也就是说从GC Roots到这个对象是不可达的,则这个对象就会被判定为可回收对象

现在在来说一下标记清除算法,该算法是将垃圾的回收分为两个阶段,分别为标记和清除,首先是标记,会采用可达性分析算法,找出可用和不可用的对象,将可用的对象的mark值设置为1,不可用的为0。然后就会进行第二个阶段,也就是清除阶段,这里会把mark为0的对象进行垃圾回收,然后将剩余对象的mark设为0,等待下一次标记。

优点

解决了引用计数算法中循环引用对象的回收问题

缺点

效率较低,在标记和清除阶段都需要遍历所有的对象,而且在GC的时候会短暂的停止应用程序,用户体验较差。通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。

标记压缩算法

标记压缩算法是在标记清除算法上做了改进和优化,标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题

优点

解决了碎片化的问题

缺点

标记压缩算法多了一步,对象移动内存位置,效率有一定的影响。

复制算法

复制算法就是将没存空间一分为二,存储时只使用其中的一块空间,当进行垃圾回收的时候,找出正在使用的对象,并将这些对象复制到另一块内存空间中,然后将该内存清空,交换两个空间的角色,实现垃圾的回收。

Jvm新生代中,Survivor区就是采用复制算法实现的垃圾回收。

步骤

在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代

优点

如果垃圾对象较多的情况下,该算法效率比较高

垃圾清理之后,内存不会出现碎片化

缺点

并不适用在垃圾较少的情况下适用,例如老年代中

分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低

总结

多指标比较:

内存效率(时间复杂度):复制算法 > 标记清除算法 > 标记压缩算法内存整齐度:标记压缩算法 = 复制算法 > 标记清除算法内存利用率:标记压缩算法 = 标记清除算法 > 复制算法

没有最好的算法,但有当下最合适的算法

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。