垃圾收集算法

87 阅读6分钟

之前个人看一些资料总是把垃圾收集算法和垃圾收集器进行混淆,造成理解起来很混乱,这里刚好抽时间进行分类整理一下,一个是梳理后更容易理解和记忆,另外也是温故而知新,对理解遗漏的也是一个补充,希望也能让大家有所收获,这篇主要来整理下垃圾收集算法。

垃圾收集算法

程序在运行中不可避免的需要使用有限的内存空间,也因为内存空间不是无限的,所以在使用完成后需要进行释放,不然就会造成内存泄露。

最初的语言都是依赖程序员来进行控制内存的申请和释放,难免就带来了复杂性和风险,随着语言的发展,渐渐由语言自身来完成这部分工作,也就是垃圾收集器,目标就是自动管理在内存中占用空间的“对象”,把不影响程序运行,不再使用的内存空间进行释放,而占用这部分空间的“对象”就是垃圾,垃圾收集器的目标就是尽可能快的识别出这些“垃圾“进行回收释放其占用内存空间,针对这个目标也渐渐出现了不同的垃圾回收算法,而不同的算法有其优缺点和适用的场景,没有优劣之分。

选择不同的垃圾收集算法来进行垃圾回收这个目标的程序叫做垃圾收集器,不同的垃圾收集器有不同的特性,例如并发、并行和不同的回收算法。

标记清除

最直接的算法,就是标记出垃圾,然后清除垃圾释放空间。

这里很明显的一个缺点就是会产生 ”内存碎片“,”内存碎片“带来的问题就是内存浪费:假如经过一段时间的垃圾回收后,有很多零碎的的小于n的空闲内存,而现在程序需要申请一个大于等于n的对象内存空间,只能新申请分配而无法复用这些零碎的内存。随着程序的不断运行,会造成也来越多的零碎空闲内存,造成越来越多的内存浪费,有比较多的空闲内存,但都无法真正用于对象分配。

复制算法

直接把可用内存分成两块,需要回收时把存活对象复制到另一块内存块,然后清除当前内存块。

这里优点是不会存在”内存碎片“,但缺点也很明显,就是对内存利用率太低,直接把可用内存减小了1/2,上来直接把可用内存分成了两份,而直接可用于分配内存的大小只有一半。

另外如果内存较大,随着程序运行,存活的对象增多,在进行回收时需要大量对象的复制,回收时间会越来越长,回收效率不高。

标记整理

直接标记出垃圾和存活对象,把存活的对象向一端移动,也就是进行整理,有些资料也叫标记压缩。

不会存在垃圾碎片,也不会浪费可用内存空间,但是同样的随着内存的增大,存活的对象越来越多,回收效率依然不高。

可见是没有完美的算法的,他们不同的特性更适用于不同的场景,而对象在内存中的分配也有着不同特性,例如大多对象是朝生夕死,部分对象长期存在,所以根据对象特性选择合适的算法更适用于实际情况,也就有了分代收集

分代算法

分代算法严格来讲不能算是一种收集算法,它是为了让不同的对象可以更好的使用适用于自己的收集算法而把内存进行分代处理的一种方式,也是现在虚拟机选择的方式

虚拟机把使用的内存划分为年轻代和老年代和永久代(后续移除了永久代),这里要说明是以现在使用的常用虚拟机到HotSpot来说明的,而这些分代名称并非《Java虚拟机规范》强制要求的。

年轻代又划分为Eden区(伊甸园)和Survivor区(幸存区),Survivor区划分为两块s0和s1,也叫form和to,总的比例为8:1:1。

为什么是8:1:1呢?因为根据历史经验统计,发现 80% 的对象存活时间都很短。于是将 Eden 区设置为年轻代的 80%,这样可以减少内存空间的浪费,提高内存空间利用率。

堆的大小可以通过参数 –Xms、-Xmx 来指定。默认新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )

即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。

其中新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to(一般简称为s0和s1),以示区分,默认Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 )

JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

分代算法过程

对象优先分配到新生代的Eden区域,当Eden区域不足以分配时,会进行垃圾回收(Yong GC),把存活的对象复制到幸存区。如果幸存区也不足分配,就会直接分配到老年代,这里大对象分配也会直接分配到老年代。老年代也不足已分配是会进行FullGC,老年代一般是使用标记整理或标记清除算法进行收集。

新生代分配产生YongGC时会选择一个Survivor区域进行回收(当前使用的S0或S1),直接把当前区域内的存活对象复制到另一个S区域,然后清除当前S区,这里两个S区域S0和S1会循环进行复制清除(所以这里使用的是复制算法),每次gc会把对象存活时间+1,超出一定次数(默认15),会晋升到老年代(直接复制到老年代)。

可以看出通过对堆内存的不同的划分,根据对象的特性对不同代选择了不同的垃圾回收算法。

新生代朝生夕死,存活的对象会比较少,所以适用复制算法,所以分为了Eden区域和两个Survivor区域,用来实现复制算法。

老年代对象一般是大对象或长期存活的对象,所以一般使用标记整理或标记清除算法。

以上就是不同的垃圾收集算法,后续我们再来看下**垃圾收集器(**许个小愿望,点赞过百马上更新^_^)

希望能用简单的方法让每个人都能看的懂,拒绝花里胡哨,高深莫测的装逼名词,一起学习。

创作不易,如果你从这篇文章中学到东西,请点一下赞或者关注,你的支持是我继续创作的最大动力,有问题也可以评论区多多交流。