Java的内存管理机制之(垃圾回收(GC)原理)
垃圾回收(GC,Garbage Collection)是Java虚拟机(JVM)中一项核心的内存管理机制,用于自动管理和回收Java堆内存中不再使用的对象,从而防止内存泄漏,提高内存利用率。
垃圾回收的基本原理
垃圾回收的基本思想是将内存中不再被使用的对象进行回收,释放其占用的内存空间。JVM通过一系列的算法和策略来判断哪些对象是可以被回收的,然后执行回收操作。
垃圾回收的判断方法
Java采用的是基于“可达性分析”的垃圾回收算法,而不是引用计数法。
-
引用计数法:
- 原理:为每个对象维护一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器就减1。当计数器为0时,表示该对象不再被引用,可以被回收。
- 缺点:无法处理循环引用的问题。因此,主流的JVM并没有采用这种算法。
引用计数法简介
引用计数法是一种简单的垃圾回收算法,它通过跟踪每个对象被引用的次数来判断对象是否应该被回收。当一个对象被创建时,引用计数器的值被设置为1;每当有一个新的引用指向该对象时,计数器的值就增加1;而当某个引用不再指向该对象时(比如引用被置为null或者指向了另一个对象),计数器的值就减少1。当计数器的值变为0时,表示该对象不再被任何引用所指向,因此可以被垃圾回收器回收。
-
可达性分析算法:
- 原理:从一组称为GC Roots的根对象开始,通过遍历对象之间的引用链,判断对象是否可以从GC Roots到达。如果对象可以通过引用链与GC Roots关联,则认为对象是存活的;否则,认为对象不再存活,可以被回收。
- GC Roots通常包括虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象等。
Java中的垃圾回收机制——可达性分析算法
可达性分析算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(即GC Roots到这个对象不可达)时,则证明此对象是不可用的,可以被回收。
Java中常见的GC Roots包括:
虚拟机栈(栈帧中的局部变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象
垃圾回收的算法
-
标记-清除算法:
- 分为标记和清除两个阶段。首先标记出所有存活的对象,然后清除所有未被标记的对象。
- 优点:速度快,不需要移动对象。
- 缺点:会产生内存碎片。
标记阶段
-
根集合识别:算法首先识别出所有的根对象(Root Objects)。在Java中,根对象通常包括:
虚拟机栈(栈帧中的局部变量表)中引用的对象。
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
-
可达性分析:从根集合开始,通过引用链逐步遍历对象,所有能够从根集合通过引用链到达的对象都被认为是“存活”的。这一过程也被称为可达性分析。
-
标记存活对象:在遍历过程中,所有被访问到的对象都会被标记为“存活”状态。这通常是通过修改对象的某个标志位来实现的。
清除阶段
-
回收内存:遍历堆中的所有对象,对于未被标记为“存活”的对象(即那些从根集合无法到达的对象),它们被认为是垃圾,JVM会回收它们所占用的内存空间。
-
内存整理(可选):在某些实现中,清除阶段后可能会进行内存整理,将存活的对象移动到堆的一端,以形成一个连续的内存空间,便于后续的内存分配。但请注意,标记-清除算法本身并不强制要求这一步。
-
-
-
标记-复制算法:
- 将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这块内存用完时,就将还存活的对象复制到另一块内存上,然后清空当前内存块。
- 优点:解决了内存碎片问题,实现简单。
- 缺点:需要两倍内存空间。
算法的执行过程:
暂停应用程序:在垃圾回收开始时,会暂停应用程序的执行,以进行垃圾回收的准备工作。这一过程也被称为“Stop-The-World”事件。
标记存活对象:垃圾回收器从根集合(如虚拟机栈、方法区中的静态属性等)开始,遍历所有的引用链,标记所有从根可达的对象为存活对象。
复制存活对象:将标记为存活的对象从来源空间复制到目标空间。这个过程中,只会复制存活的对象,而不会复制那些已经被回收的对象。
清理来源空间:复制完成后,来源空间中剩下的对象都是不再被使用的,可以被清理掉。此时,来源空间变成了空的,可以用于下一次的分配。
交换空间:在下一次垃圾回收时,目标空间会变成新的来源空间,而原来的来源空间则变成新的目标空间。这个过程交替进行,以确保内存的有效利用。
特点
简单高效:由于每次只处理一半的内存空间,因此理论上垃圾回收的最大暂停时间不会随着堆大小的增加而增加。
无内存碎片:由于每次都是从空的半空间开始分配对象,因此不会产生内存碎片。
内存利用率低:由于需要同时维护两个相等的内存空间,因此在实际应用中,内存的利用率会降低。这意味着如果有4G的内存,那么实际可用于应用程序的内存可能只有2G。
适用于短生存期的对象:由于每次都需要复制存活的对象,因此对于长生存期的对象来说,这种算法的效率并不高。因此,它更适合用于管理那些生命周期较短的对象。 -
标记-整理算法:
- 结合了标记-清除和标记-复制的优点。标记阶段与标记-清除算法相同,但在清除阶段,不是直接清除未标记的对象,而是让所有存活的对象都向一端移动,然后清理掉端边线以外的内存。
- 优点:解决了内存碎片问题,不需要额外内存空间。
- 缺点:效率相对较低。
标记阶段:
遍历堆中的所有对象,通过根集合(如栈帧中的局部变量表、静态变量等)开始,标记出所有从根集合可达(即存在引用链)的对象。
未被标记的对象被视为垃圾,即不可达对象,它们将在后续的GC过程中被清理。
整理(或压缩)阶段:
将所有存活的对象移动到堆的一端,确保它们是连续存储的。
清理掉存活对象之后的所有空间,即删除所有垃圾对象,并调整存活对象的引用,以反映它们在堆中的新位置。
这一步的目的是减少堆内存的碎片化,提高内存使用的效率。
-
三色标记算法:
- 一种优化标记过程的算法,将对象分为白色、灰色和黑色三种状态。通过初始标记、并发标记和重新标记三个阶段来标记对象,以减少STW(Stop The World)的时间。
三色标记算法对象三种状态的转换过程
三色标记算法中, 对象被分为三种状态: 白色 、 灰色 和 黑色 。- 白色表示对象尚未被扫描,即未被标记为存活对象。
- 灰色表示对象已经被扫描,但其引用的其他对象尚未被扫描。
- 黑色表示对象已经被扫描,并且其引用的其他对象也已经被扫描。
转换过程如下:
- 初始阶段,所有对象都被标记为白色。
- 从根对象开始,递归地遍历对象图,将遇到的对象标记为灰色,并将其引用的对象添加到待扫描队列中。
- 从待扫描队列中取出对象,将其标记为黑色,并将其引用的对象添加到待扫描队列中。
- 重复上述过程,直到待扫描队列为空。
- 结束后,所有未被标记为黑色的对象即为垃圾对象,可以被回收。
三色标记算法使用场景
三色标记算法在并发可达性分析中会被使用 。它是一种广泛应用于并发环境下的垃圾回收算法,用于确定一个对象是否可以被其他对象访问,从而判断其是否为垃圾对象并进行回收1。该算法通过将对象标记为白色、灰色和黑色三种不同的颜色来进行可达性分析。在并发环境下,由于线程间的共享数据和相互依赖,对象的引用关系可能会发生变化,因此需要使用三色标记算法来有效地处理这种动态变化的引用关系,以及解决可能出现的浮动垃圾问题1。通过这种算法,垃圾回收器可以更准确地判断对象的存活状态,并进行有效的垃圾回收。
三色标记算法与其他垃圾回收算法的比较
三色标记算法与其他垃圾回收算法相比,具有独特的特点和适用场景。三色标记算法是一种可达性分析算法,它将对象分为白色、灰色和黑色三种,通过深度优先搜索进行标记和回收。与其他算法相比,三色标记算法在并发环境下具有优势,能有效处理动态变化的引用关系,并解决浮动垃圾问题12。
相比之下,标记复制算法将内存分为两块,每次只使用一块,回收时复制存活对象到另一块。它适用于新生代,因为新生代对象存活率低,复制成本较低14。
而标记清除和标记整理算法则更适用于老年代,因为它们能更好地处理存活率较高的对象,尽管它们的效率相对较低14。
- 一种优化标记过程的算法,将对象分为白色、灰色和黑色三种状态。通过初始标记、并发标记和重新标记三个阶段来标记对象,以减少STW(Stop The World)的时间。
垃圾回收的触发条件
1. 内存分配达到阈值
在Java等语言中,当堆内存中的空闲空间不足以满足新对象的分配需求时,垃圾回收器(GC)可能会被触发。这个阈值可能是固定的,也可能是根据运行时情况动态调整的。
2. 特定区域的内存满了
- 年轻代(Young Generation)满了:在JVM中,新创建的对象通常首先被分配到年轻代的Eden区。当Eden区满了时,会触发Minor GC(也称为Young GC),以回收不再被使用的对象并清理出空间。
- 老年代(Old Generation)满了:当老年代没有足够的空间容纳新的对象或年轻代中晋升的对象时,会触发Major GC(也称为Full GC),以回收整个堆中的垃圾对象。
- 元空间(Metaspace)满了(Java 8及以上版本):元空间用于存储类的元数据。当元空间满了时,也会触发Full GC。
3. 显式调用垃圾回收
在某些语言中,如Java,可以通过调用System.gc()
方法显式地建议JVM进行垃圾回收。但请注意,这只是一个建议,JVM可以忽略它,具体是否执行以及何时执行垃圾回收由JVM的内部机制决定。
4. 系统内存压力
当系统内存不足时,操作系统可能会向正在运行的应用程序发送信号,要求其释放不必要的内存。在这种情况下,垃圾回收器可能会被触发以响应内存压力。
5. 特定垃圾回收器的策略
不同的垃圾回收器可能有不同的触发条件。例如,G1垃圾回收器在年轻代和老年代的内存使用达到一定阈值时,会触发混合回收(Mixed GC),即同时回收年轻代和部分老年代的区域。而CMS(Concurrent Mark Sweep)垃圾回收器则会在老年代使用达到一定比例时,触发并发标记和清理过程。