垃圾回收阶段算法
标记-清除算法
执行过程:
当堆中的有效内存被耗尽的时候,就会停止整个程序,第一项是标记,第二项是清除.
标记:Collector从根节点开始遍历.标记所有被引用的对象,标记的并不是即将要清除的对象.
清除:Collector在堆内存中从头到尾的遍历,如果发现那个对象没有被Header标记为可达对象,则对其进行回收.
优点:
非常基础和常见的垃圾收集算法容易理解
缺点:
效率不高,
在进行GC时,需要停掉整个程序,用户体验极差.
这种方式清理出来的空闲内存是不连续的,产生内存碎片.必须要维护一个内存空间.
注意:何为清除
这里所谓的清除并不是置空,而是把需要清除的对象地址保存到空闲的内存空间中.下次有新对象需要加载时,判断垃圾的位置是否足够,如果够,就存放(也就是覆盖掉原来的垃圾对象的地址).
复制算法:
为了解决上述算法效率的问题,复制算法可将内存分为大小相等的俩快,每次使用一块.在垃圾回收时将存活对象复制到另一个未被使用的内存中,然后清除正在使用的内存中的所有对象.交换俩个内存的角色,最后完成垃圾回收.
优点:
没有标记和清除的过程,实现简单运行高效
复制过去后保证内存空间的连续性,不会出现碎片问题
缺点:
需要俩倍的内存空间;
对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要委会region之间大小的引用关系,不管内存空间或者占用时间-开销都不小.
标记压缩算法
为什么出现?
复制算法的高效性体现在存活对象少,垃圾对象多的情况下,适用于新生代.但是老年代存在大量的存活对象.入伙依旧使用复制算法,成本就会增加.
标记-清除算法可以应用于老年代,但是该算法效率较低,且伴随着内存碎片.
标记-压缩算法执行过程
第一阶段和标记清除一样,从根节点遍历标记所有被引用的对象.
第二阶段将所有的存活对象压缩到一端,按顺序排放,之后,清理边界外所有的空间.
标记-压缩算法与标记-清除算法的比较:
标记压缩算法可以理解为在标记-清除算法的完成阶段在来一次内存碎片整理,因此,它也可以被称之标记-清除-压缩算法.
二者的本质差异是标记-清除算法是一种非移动性的回收算法,而标记-压缩算法则是移动性的算法.是否移动的回收存活对象是一种优缺点并存的风险决策.
显而易见,标记的存活对象会被整理,按照内存地址依次排列,而未被标记的对象将会被清理.如此一来,当我们需要给新对象分配内存时,JVM持有一个内存的起始地址即可,这比维护一个空闲列表的开销要少的多.
优点:
消除了标记-清除算法中,内存区域分散的缺点,
消除了复制算法内存减半的高额代价.
缺点:
从效率上说,他要低于复制算法.
移动对象时,如果对象被其他对象引用,还需要调整引用地址.
移动过程中,需要全部暂停用户应用程序 即:STW
小结:
效率上说,复制算法是当之无愧的第一,但是却浪费了大量的内存.
而为了尽量兼顾上述三个指标,标记-压缩算法显得更加平滑一点.但是效率上差一点.
它比标记清除算法多了一个标记阶段.
比复制算法多了一个整理内存的阶段.
分代收集算法
上述算法中都有着自己独特的优势,并不能有一个算法取而代之.分代收集算法应运而生.
由于不同的对象它的生命周期是不同的,因此可以在不同阶段使用不同的算法.一般把java堆分为新生代和老年代,这样可以提高垃圾回收效率,
在java程序运行时,会产生大量的对象,而他们的生命周期也各不相同,
目前,几乎所有的GC都会采用分代的思想区垃圾回收.
年轻代:
具有内存空间小,回收频繁,对象生命周期短,存活率低等特性
这时就需要复制算法来进行垃圾回收,而复制算法所带来的内存利用率不高的问题,可以用Hotspot中的俩个survivor的设计得以缓解.
老年代:
具有内存空间大,存活率高,对象生命周期长,回收远远不及年轻代频繁.
这时就需要标记-清除和标记-压缩算法来混合实现垃圾回收
小结:分代的思想被现有的虚拟机广泛使用,目前几乎所有的GC都区分老年代和青年代.
增量收集算法和分区算法
增加收集算法:
上述现有的算法,在执行时都会处于一种stop-the-world的状态;而在这种状态下,所有的应用程序都将会被挂起,在垃圾回收算法执行完成后才会继续执行.而有时垃圾回收时间较长这就会导致用户体验感极差.为了解决这个问题,即增量算法就诞生了.
基本思想:
如果一次性垃圾回收,这就好导致系统停顿时间较长,那么可以让垃圾回收和应用程序交替进行,每次回收只对一小部分内存空间进行,然后恢复程序运行,反复操作即可.增量收集算法是基于标签-清除和复制算法.增量收集算法通过对线程之间的冲突进行管理,已达到标记-清除和复制算法的正常进行.
缺点:
在垃圾回收过程中,虽然间接的执行了应用程序,减少了系统停顿时间,但是在交接的过程中,线程之间的切换和上下文的消耗,会导致垃圾回收成本提高.
分区算法:
一般来说,堆空间内存越大,GC所需要的时间越长,系统停顿也就越长.所以分区算法是将一个大的堆空间分为若干个小区间进行GC处理,以减少系统的停顿时间.
分代算法是将对象的生命周期分为长度俩个部分,而分区则是将一个大的堆空间分为若干个小区间,他们之间独立使用,独立回收,这样可以控制一次回收多少小区间.