GC垃圾回收介绍及GC算法详解
目录
引言
GC的作用域
什么是垃圾回收?
常见的GC算法
1.引用计数法
2.复制算法
3.标记清除
4.标记整理
小总结
5.分代收集算法
ps:可达性分析算法?
可达性分析的作用
可达性分析与垃圾回收算法的关系
结论
引言
在编程世界中,内存管理是一个至关重要的话题。对于使用自动内存管理的语言(如Java、Python、Go等),垃圾回收(Garbage Collection, GC)机制是确保程序高效运行的关键。本文将介绍垃圾回收的基本概念,并详细讲解四种常见的垃圾回收算法。
GC的作用域
记住GC口诀: 分代收集算法
次数频繁Young区,次数较少Old区,基本不动Perm(永久区)区
什么是垃圾回收?
垃圾回收是一种自动内存管理机制,用于识别和释放不再被程序使用的内存。其主要目的是防止内存泄漏,减少程序员手动管理内存的负担,并提高程序的稳定性和性能。
先看下一个对象的历程:
JVM 在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代
因此GC按照回收的区域又分了两种类型,一种是普通的GC(minor GC),一种是全局GC (major GC or Full GC)
普通GC(轻GC):只针对新生代区域的GC
全局GC(重GC):针对老年代的GC,偶尔伴随对新生代的GC以及对永久代的GC
常见的GC算法
1.引用计数法
每个对象有一个引用计数器,当对象被引用一次则计数器加1,当对象引用失效一次,则计数器减1,对 于计数器为0的对象意味着是垃圾对象,可以被GC回收。
2.复制算法
年轻代中使用的是Minor GC,采用的就是复制算法
Minor GC 会把Eden中的所有活的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的 活的对象就被移动到Old generation中,也就是说,一旦收集后,Eden就是变成空的了 当对象在Eden(包括一个Survivor区域,这里假设是From区域)出生后,在经过一次Minor GC后,如 果对象还存活,并且能够被另外一块Survivor区域所容纳 (上面已经假设为from区域,这里应为to区 域,即to区域有足够的内存空间来存储Eden 和 From 区域中存活的对象),则使用复制算法将这些仍然 还活着的对象复制到另外一块Survivor区域(即 to 区域)中,然后清理所使用过的Eden 以及Survivor 区域(即form区域),并且将这些对象的年龄设置为1,以后对象在Survivor区,每熬过一次Minor GC,就将这个对象的年龄 + 1,当这个对象的年龄达到某一个值的时候(默认是15岁,通过 XX:MaxTenuringThreshold 设定参数)这些对象就会成为老年代。
-XX:MaxTenuringThreshold 任期门槛=>设置对象在新生代中存活的次数
面试题:如何判断哪个是to区呢?一句话:谁空谁是to
工作原理:
-
将内存分为两个相等的区域:From空间和To空间。
-
复制阶段:从From空间复制所有存活对象到To空间。
-
清理阶段:清空From空间,然后交换From和To空间的角色。
优点:
-
简单高效,没有内存碎片问题。
缺点:
-
需要双倍内存空间,适用于存活对象较少的情况。
3.标记清除
说明:老年代一般是由标记清除或者是标记清除与标记整理的混合实现
什么是标记清除? 回收时,对需要存活的对象进行标记; 回收不是绿色的对象
当堆中的有效内存空间被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项 工作,第一项则是标记,第二项则是清除。
标记:从引用根节点开始标记所有被引用的对象,标记的过程其实就是遍历所有的GC Roots ,然后将所 有GC Roots 可达的对象,标记为存活的对象。
清除: 遍历整个堆,把未标记的对象清除。
缺点:这个算法需要暂停整个应用,会产生内存碎片。
工作原理:
-
标记阶段:从根对象(如全局变量、栈中的对象)出发,遍历所有可达对象,并标记它们为存活。
-
清除阶段:遍历整个堆内存,释放未被标记的对象所占用的内存。
优点:
-
实现简单,适用于大多数场景。
缺点:
-
会产生内存碎片,可能导致后续内存分配效率降低。
4.标记整理
在整理压缩阶段,不再对标记的对象作回收,而是通过所有存活对象都像一端移动,然后直接清除边界 以外的内存。可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被 清理掉,如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比 维护一个空闲列表显然少了许多开销。
标记、整理算法不仅可以弥补 标记、清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内 存减半的高额代价;
工作原理:
-
标记阶段:与标记-清除算法相同,标记所有存活对象。
-
整理阶段:将所有存活对象移动到内存的一端,然后清理边界以外的内存。
优点:
-
解决了内存碎片问题,提高了内存利用率。
缺点:
-
整理阶段需要移动对象,增加了时间开销。
小总结
内存效率:复制算法 > 标记清除算法 > 标记整理算法 (时间复杂度)
内存整齐度:复制算法 = 标记整理算法 > 标记清除算法
内存利用率:标记整理算法 = 标记清除算法 > 复制算法
可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所提到的三个指标,标记整理算法相对来说更平滑一些 , 但是效率上依然不尽如人意,它比复制算法多了 一个标记的阶段,又比标记清除多了一个整理内存的过程
难道就没有一种最优算法吗?猜猜看,下面还有
答案 : 无,没有最好的算法,只有最合适的算法 。 -----------------> 分代收集算法。
5.分代收集算法
工作原理:
-
基于对象的生命周期将内存分为不同的代(如年轻代和老年代)。
-
年轻代:使用复制算法,因为大多数对象在年轻代中很快死亡。
-
老年代:使用标记-清除或标记-整理算法,因为老年代中的对象存活时间较长。
优点:
-
针对不同生命周期的对象采用不同的回收策略,提高了回收效率。
缺点:
-
实现复杂,需要维护多个代的内存区域。
ps:可达性分析算法?
可达性分析算法(Reachability Analysis)本身并不是一个独立的垃圾回收算法,而是一种用于判断对象是否存活的技术。它是许多垃圾回收算法的基础,尤其是**标记-清除算法(Mark-Sweep)和标记-整理算法(Mark-Compact)**的核心部分。
可达性分析的作用
可达性分析通过从根对象(如全局变量、栈中的局部变量、静态变量等)出发,遍历所有被引用的对象,标记这些对象为“存活”。未被标记的对象则被认为是“垃圾”,可以被回收。
可达性分析与垃圾回收算法的关系
-
标记-清除算法(Mark-Sweep):
-
使用可达性分析来标记所有存活对象。
-
在清除阶段,释放未被标记的对象的内存。
-
-
标记-整理算法(Mark-Compact):
-
同样使用可达性分析来标记存活对象。
-
在整理阶段,将存活对象移动到内存的一端,然后清理剩余的内存。
-
-
分代收集算法(Generational):
-
在年轻代和老年代中,都可能使用可达性分析来判断对象的存活状态。
-
年轻代通常使用复制算法,而老年代可能使用标记-清除或标记-整理算法。
-
-
复制算法(Copying):
-
虽然复制算法的主要特点是复制存活对象,但它也需要通过可达性分析来确定哪些对象是存活的。
-
结论
垃圾回收是现代编程语言中不可或缺的一部分,它通过自动管理内存,极大地简化了程序开发。不同的垃圾回收算法各有优缺点,适用于不同的应用场景。理解这些算法的原理和适用场景,有助于我们编写更高效、更稳定的程序。