016、大厂面试题:JVM中有哪些垃圾回收算法,每个算法各自的优劣?

JVM中有哪些垃圾回收算法,每个算法各自的优劣?
目录:
1、前文回顾
2、复制算法的背景引入
3、一种不太好的垃圾回收思路
4、一个合理的垃圾回收思路
5、复制算法有什么缺点?
6、复制算法的优化:Eden区和Survivor区
7、新生代垃圾回收的各种“万一”怎么处理?
8、昨日思考题解答
9、今日思考题

“loadReplicasFromDisk()”方法中创建了对象,此时对象就就会分配在新生代其中一块内存空间里
而且是由“main线程”的栈内存中的“loadReplicasFromDisk()”方法的栈帧内的局部变量来引用的,如下图所示。


接着大家想象一下,假设与此同时,代码在不停的运行,然后大量的对象都分配在了新生代内存的其中一块内存区域里,也只会分配在那块区域里
而且分配过后,很快就失去了局部变量或者类静态变量的引用,成为了垃圾对象
此时如下图所示。

接着这个时候,新生代内存那块被分配对象的内存区域基本都快满了,再次要分配对象的时候,发现里面内存空间不足了。
那么此时就会触发Minor GC去回收掉新生代那块被使用的内存空间的垃圾对象。
那么回收的时候是怎么做的呢?
3、一种不太好的垃圾回收思路
假设现在采用的垃圾回收思路,就是直接对上图中被使用的那块内存区域中的垃圾对象进行标记。
也就是根据上篇文章讲的那套思路,标记出哪些对象是可以被垃圾回收的,然后就直接对那块内存区域中的对象进行垃圾回收,把内存空出来。
大家想想,这种思路好吗?
这种思路去垃圾回收,可能会在回收完毕之后造成那块内存区域看起来跟下图一样。

看上面的图,不知道大家发现什么没有,在那块被使用的内存区域里,回收掉了大量的垃圾对象,但是保留了一些被人引用的存活对象
但是呢,存活对象在内存区域里东一个西一个,非常的凌乱,而且造成了大量的内存碎片。
那么什么是内存碎片呢?
我们再看下面的图我用红线标记出来的区域,那些就是所谓的内存碎片。

看到了吗?在各种凌乱的存活对象的中间,出现了大量的红圈圈出来的内存碎片
这些内存碎片的大小不一样,有的可能很大,有的可能很小。
那么内存碎片太多会造成什么问题呢?
内存浪费
啥意思?比如现在打算分配一个新的对象,尝试在上图那块被使用的内存区域里去分配
此时如下图所示,可能因为内存碎片太多的缘故,虽然所有的内存碎片加起来其实有很大的一块内存,但是因为这些内存都是碎片式分散的,所以导致没有一块完整的足够的内存空间来分配新的对象。

所以这种直接对一块内存空间回收掉垃圾对象,保留存活对象的方法,绝对是不可取的
因为内存碎片太多,就是他最大的问题,会造成大量的内存浪费,很多内存碎片压根儿是没法使用的。
4、一个合理的垃圾回收思路
那么能不能用一种合理的思路来进行垃圾回收呢?
可以!这个时候上图中一直没派上用场的另外一块空白的内存区域就出场了。
首先,并不是按照上述思路直接对已经使用的那块内存把垃圾对象全部回收掉,然后保留全部存活对象。
而是先对那块在使用的内存空间标记出里面哪些对象是不能进行垃圾回收的,就是要存活的对象
然后先把那些存活的对象转移到另外一块空白的内存中,如下图。

不知道大家发现这里的玄机没有?
没错,通过把存活对象先转移到另外一块空白内存区域,我们可以把这些对象都比较紧凑的排列在内存里
这样就可以让被转移的那块内存区域几乎没有什么内存碎片,对象都是按顺序排列在这块内存里的。
然后那块被转移的内存区域,是不是多出来一大块连续的可用的内存空间?
此时就可以将新对象分配在那块连续内存空间里了,如下图。

这个时候,再一次性把原来使用的那块内存区域中的垃圾对象全部一扫而空,全部给回收掉,空出来一块内存区域,如下图。
这就是所谓的“复制算法“,把新生代内存划分为两块内存区域,然后只使用其中一块内存
待那块内存快满的时候,就把里面的存活对象一次性转移到另外一块内存区域,保证没有内存碎片
两块内存区域就这么重复着循环使用。
5、复制算法有什么缺点?
复制算法的缺点其实非常的明显,如果按照上述的思路,大家会发现,假设我们给新生代1G的内存空间,那么只有512MB的内存空间是可以用的
另外512MB的内存空间是一直要放在那里空着的,然后512MB内存空间满了,就把存活对象转移到另外一块512MB的内存空间去
从始至终,就只有一半的内存可以用,这样的算法显然对内存的使用效率太低了。
6、复制算法的优化:Eden区和Survivor区
之前我给大家分析过,系统运行时,对JVM内存的使用模型,大体上就是我们的代码不停的创建对象然后分配在新生代里,但是一般很快那个对象就没人引用了,成了垃圾对象。
接着一段时间过后,新生代就满了,此时就会回收掉那些垃圾对象,空出来内存空间,给后续其他的对象来使用。
但是我们之前分析过,其实绝大多数的对象都是存活周期非常短的对象,可能被创建出来1毫秒之后就没人引用了,他就是垃圾对象了。
1个Eden区,2个Survivor区,其中Eden区占80%内存空间,每一块Survivor区各占10%内存空间,比如说Eden区有800MB内存,每一块Survivor区就100MB内存,如下图。

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

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

到底一个存活对象要在新生代里这么来回倒腾多少次之后才会被转移都老年代去?

上述代码下,如果垃圾回收,会回收ReplicaFetcher对象吗?为什么?
End
版权:公众号儒猿技术窝
未经许可不得传播,如有侵权将追究法律责任