GC是如何确定垃圾的?和垃圾回收算法

xiaoxiao2021-02-28  20

1.什么是垃圾?

对象占用了内存的资源,但是你却永远使用不了该对象了。一般情况是没有引用的对象,或者是没有执行外部引用的都对象(因为会有环形垃圾,它虽然有引用指引,但是无法被外部引用了)

2.如何确定垃圾?

             1.引用计数:有一个引用指向该对象就加一,什么时候值为0了,它就是垃圾。这种方法是不可行了,会有循环引用的问题

             2.正向可达:从roots对象开始,计算追踪可以到达的对象,那么不可到达的对象就被视为垃圾。

3.垃圾回收算法

1.标记清除  Mark-Sweep

           是现代清除回收算法的思想基础

           缺点:内存不连续,碎片化

           实现过程:  两个阶段

               1.标记阶段: 通过  正向可达算法  首先通过根节点,标记所有从根节点开始的可达对象,因此未标记的对象就是未被引用的就是垃圾对象

                2.清除阶段: 清楚所有未被标记的对象

2.复制算法 Copying

         适合场景: 垃圾对象很多,存活的对象较少(例如 新生代),一般在新生代内存区使用

         优点: 效率很高,压缩,可确保回收的内存空间没有碎片化

         缺点: 将系统内存折半, 内存浪费,只能用一半内存

         算法思想:将原有的内存空间分为两块相同的存储空间,每次只使用一块,在垃圾回收时,将正在使用的内存块中存活对象复制到未使用的那一块内存空间中,之后清除正在使用的内存块中的所有对象,完成垃圾回收。

A、B两块相同的内存空间(原有内存空间折半得到的两块相同大小内存空间AB),A在进行垃圾回收,将存活的对象复制到B中,B中的空间在复制后保持连续。完成复制后,清空A。并将空间B设置为当前使用内存空间。

在 新生代 中的两个survivor区(也称from和to区) 和 eden到survivor,就是使用Copying算法,同一时间只能有一个被做当前内存空间使用,另一个只有在垃圾回收时才发挥作用

当发生GC时,eden空间中存活的对象会被复制到未使用的survivor中(图中的to)

正在使用的survivor空间(图from)中的年轻对象也会被复制到to空间中(大的对象或者是老的对象会直接进入老年代,如果to空间满了对象也会进入老年代)

此时eden和from空间中的剩余对象就是垃圾对象,直接清空。

3.标记-压缩 Mark-compact

基于标记清除算法做了优化,回收后的内存空间是连续的

一般使用在老年代,因为在老年代垃圾比较少,还是用复制算法的话,成本会比较高

优点: 效率还不错(比Copying算法低),压缩,回收后的空间是连续的

算法原理:

从根节点开始,对所有可达的对象做一次标记,然后将所有存活的对象压缩到内存空间的一端,之后,清理边界外所有的空间

这样做避免了碎片的产生,又不需要两块相同的内存空间。

4. jvm是使用分代算法

分代算法的思想是: 根据内存空间中对象的不同特点,选择不同的算法

新生代对象的特点: 大约90%的对象会被回收,存活得对象不多,所以要复制的对象不多。所以新生代适合使用复制算法

老年代对象的特点: 存活率高,再使用Copying算法将复制大量的对象,会消耗很多时间,不可取。适合使用标记-压缩算法

卡表: 为了提高 新生代 垃圾回收的速度

虚拟机可能使用一种叫卡表的数据结构,卡表为一个 比特位 集合,每一个比特位可以用来表示老年代的某一区域中的所有对象是否持有新生代对象的引用

这样以来,新生代GC时,可以不用花大量时间扫描所有老年代对象,来确定每一个对象的引用关系,而可以先扫描卡表,只有当卡表的标记为1时,才需要扫描给定区域的老年代对象,而卡表为0的所在区域的老年代对象,一定不含有新生代对象的引用。

如下图表示:

卡表中每一位老年代4KB的空间,卡表记录为0的老年代表示没有任何引用指向新生代。只有卡表为1的区域才有对象包含新生代对象的引用

因此,在新生代GC时,只需要扫描卡表为1所在的老年代空间,使用这种方式可以大大加快新生代的回收速度

转载请注明原文地址: https://www.6miu.com/read-1400108.html

最新回复(0)