JVM年轻代的垃圾回收算法-复制算法

1.背景引入

针对年轻代的垃圾回收算法,叫做复制算法

如下图,首先吧年轻代的内存分为两块:

假设有如下代码,在“loadReplicasFromDisk()”方法中创建了对象,此时对象会分配在年轻代其中一块内存空间里

而且是由“main线程”的栈内存中的“loadREplicasFromDisk()”方法的栈帧内的局部变量来引用的

public class Kafka {
    public static void main(String[] args) {
        loadReplicasFromDisk();
    }
    
    private static void loadReplicasFromDisk() {
        ReplicaManager replicaManager = new ReplicaManager();
    }
}

假设与此同时,代码在不停地运行,然后大量的对象都分配在了新生代内存的其中一块内存区域里,也只会分配在那块区域里

而且分配过后,很快就失去了局部变量或者类静态变量的引用,成为了垃圾对象,如下图

接着这个时候,年轻代内存那块被分配对象的内存区域基本快满了,再次分配对象的时候,发现内存空间不足了。

此时就会出发Minor GC 去回收年轻代那块被使用的内存空间的垃圾对象。

那么回收掉时候是怎么做的呢?

 

2.不太好的垃圾回收思路

假设Minor GC时,直接对内存区域中的垃圾对象进行标记 然后就直接对那块内存区域中的对象进行垃圾回收,把内存空出来

这种思路非常不好,可能会出现下图所示的情况

虽然回收掉了垃圾对象,保留了存活对象,但是他们在内存区域里同一个西一个,非常凌乱,造成了大量的内存碎片。

如图红圈标记出来的,就是内存碎片,他们有的可能很大,有的可能很小。

内存碎片过多,就会造成内存浪费

比如现在打算分配一个新的对象,尝试在上图那块被使用的内存区域里去分配

可能因为内存碎片太多的缘故,虽然所有的内存碎片加起来其实有很大的一块内存,但是因为这些内存是碎片式分散的,所以导致没有一款完整的足够的内存空间来分配新的对象。

 

所以这种直接对一块内存空间回收掉垃圾对象,保留存活对象的方法,绝对是不可取的。

因为内存碎片过多,是他最大的问题,会造成大量的内存浪费,很多内存碎片压根没法使用。

 

3.合理的垃圾回收思路

首先对那块在使用的内存空间标记处里面哪些对象是不能进行垃圾回收的,就是要存活的对象

然后先把哪些存活的对象转移到另外一块空白的内存中,如下图

先把存活对象转移到另外一块空白内存区域,可以吧这些对象都比较紧凑的排列在内存里

然后那块被转移的内存区域,就会多出一大块连续的可用的内存空间

此时就可以将新对象分配在那块连续内存空间里了

这就是“复制算法”,把年轻代内存划分为两块内存区域,然后只使用其中一块内存

待那块内存快满的时候看,就把里面的存活对象一次性转移到另外一个内存区域,保证没有内存碎片

接着一次性回收原来那块内存区域的垃圾对象,再次空出来一块内存区域。两块内存区域就这么重复着循环使用

 

4.复制算法缺点

复制算法的缺点非常明显,如果按照上述的思路,假设我们给年轻代1G的内存空间,那么只有512MB的内存空间是可以用的

另外512MB的内存空间是一直要放在那里空着的,然后512MB内存空间满了,就把存活对象转移到另外一块512MB的内存空间去

从始至终,就只有一半的内存可以用,这样的算法显然对内存的使用效率太低了。

 

5.复制算法的优化:Eden区和Survivor区

 

实际上真正的复制算法会做出如下优化。把年轻代内存区域分为三块

 

1个Eden区,2个Survivor区,其中Eden区占80%内存空间,每一块Survivor区各占10%的内存空间

比如说Eden区有800MB内存,每一块Survivor区就100MB内存,如下图;

平时可以使用的,就是Eden区和其中一块Survivor区,那么相当于就是有900M的内存是可以使用的。

 

但是刚开始对象是分配在Eden区的,如果Eden区快满了,就会出发垃圾回收

此时就会把Eden区中的存活对象一次性转移到一个空的Survivor区,接着Eden区就会被情况,然后再次分配新对象到Eden区

如下图所示,Eden区和一块Survivor区里有对象,其中Survivor区里放的是上一次Minor GC后存活的对象

如果下次Eden区满,那么再次出发Minor GC,就会把Eden区和放着上一次Minor GC 后存活对象的Survivor区内存的存活对象转移到另外一块Survivor区去。

 

因为每次垃圾回收可能存活下来的对象就1%,所以设计到时候就留了一块100MB的内存空间来存放垃圾回收后转移过来的存活对象

 

比如Eden区+一块Survivor区有900MB的内存空间都占满了,但是垃圾回收之后,可能就10MB对象是存活的。

此时就把那10MB的存活对虾转移到另外一块Survivor区域就可以,然后再一次把Eden区和之前使用的Survivor区里的垃圾对象全部回收掉、

 

接着新对象继续分配在Eden区和另外那块开始被使用的Survivor区,然后始终保持一块Survivor区是空着的,就这样一直循环使用这三块区域

这么做最大的好处,就是只有10%的内存空间是被闲置的,90%的内存都被使用上了;无论是垃圾回收的性能,内存碎片的控制,还是说内存使用的效率,都非常的好。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值