Skip to content

一、什么是垃圾

  • 垃圾 是指在运行程序中 没有任何指针指向的对象,这个 对象 就是需要 被回收的垃圾
  • 如果不及时对内存中的垃圾进行清理,这些 垃圾对象占用内存空间会一直保留到应用程序结束被保留的空间无法被其他对象使用,甚至可能 导致内存溢出

二、如何回收垃圾

  • 通过 GC 收集器进行垃圾回收。
  • GC 收集器产生背景,是有如下算法。

引用计数法

注意: 在 java 中是没有使用引用计数法

  • 引用计数法产生背景:

    • 在某些编程语言中,对象的内存分配和释放是由开发者手动管理的。当一个对象不再被使用时,需要显式地释放它所占用的内存,否则可能会导致内存泄漏。
  • 当对象被 引用 时,计数器 加 1

  • 当对象的引用被 解除时,计数器 减 1

  • 当引计数器为 0 时对象会被回收

在 Java 中,引用计数法并不是主要的内存管理技术,因为 Java 使用垃圾回收器(Garbage Collector)来自动管理对象的内存。垃圾回收器通过检测不再被引用的对象,并自动释放它们所占用的内存。

然而,为了了解引用计数法的工作原理,我们可以创建一个简单的示例来模拟引用计数的概念。请注意,这只是一个示例,不建议在实际开发中使用引用计数法。

java
package com.calvin.jvm.gc.algorithm;

/**
 * 引用计数对象
 *
 * @author calvin
 * @date 2023/10/08
 */
class ReferenceCountedObject {
    private int referenceCount;

    /**
     * 引用计数对象
     */
    public ReferenceCountedObject() {
        referenceCount = 0;
    }

    /**
     * 添加引用
     */
    public void addReference() {
        referenceCount++;
    }

    /**
     * 删除引用
     */
    public void removeReference() {
        referenceCount--;
    }

    /**
     * 获取引用计数
     *
     * @return int
     */
    public int getReferenceCount() {
        return referenceCount;
    }
}

/**
 * 引用计数示例
 *
 * @author calvin
 * @date 2023/10/08
 */
public class ReferenceCountingExample {

    /**
     * 主要
     *
     * @param args arg 参数
     */
    public static void main(String[] args) {

        ReferenceCountedObject obj1 = new ReferenceCountedObject();
        // 增加引用计数
        obj1.addReference();

        // obj2 引用 obj1
        ReferenceCountedObject obj2 = obj1;
        // 增加引用计数
        obj2.addReference();
        // 输出引用计数
        System.out.println("obj1 reference count: " + obj1.getReferenceCount());
        // 减少引用计数
        obj1.removeReference();
        System.out.println("obj1 reference count: " + obj1.getReferenceCount());

        // 减少引用计数
        obj2.removeReference();
        System.out.println("obj1 reference count: " + obj1.getReferenceCount());
    }
}

/**
 * 输出结果:
 *
 * obj1 reference count: 2
 * obj1 reference count: 1
 * obj1 reference count: 0
 */

在这个示例中,我们创建了一个名为 ReferenceCountedObject 的类,它包含一个引用计数器 referenceCount

  • 通过 addReference()方法增加引用计数,
  • 通过 removeReference() 方法减少引用计数。
  • getReferenceCount() 方法用于获取当前的引用计数。

ReferenceCountingExample 类的 main() 方法中,我们创建了一个 ReferenceCountedObject 对象 obj1 ,并增加了一个引用计数。然后,我们创建了另一个对象 obj2 ,并将其设置为引用 obj1 ,同时增加了一个引用计数。

最后,我们通过调用 removeReference() 方法来减少引用计数,并通过调用 getReferenceCount() 方法来获取当前的引用计数。输出结果将显示引用计数的变化。

请注意,这个示例只是为了演示引用计数法的概念,并不适用于实际的内存管理。在 Java 中,垃圾回收器会自动处理对象的内存释放,无需手动管理引用计数。

注意: 引用计数法存在无法处理循环引用的情况问题

可达性分析算法

  • 判定对象是否存活的。

  • 该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点。

  • 从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。

  • 例如:可达分析流程图

对象 object 5、object 6、object 7 虽然互有关联,但是它们到 GC Roots 是不可达的, 因此它们将会被判定为可回收的对象.

在 Java 技术体系里面,固定可作为GC Roots的对象包括以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象, (例如: 各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等)。
  • 方法区中类静态属性引用的对象,(例如: Java 类的引用类型静态变量)。
  • 方法区中常量引用的对象,(例如: 字符串常量池(String Table)里的引用)。
  • 本地方法栈中JNI(即通常所说的Native方法)引用的对象。
  • Java虚拟机内部的引用,(例如: 基本数据类型对应的 Class 对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器)。
  • 所有被同步锁(synchronized关键字)持有的对象。
  • 反映 Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存 等。

三、垃圾回收算法

标记清除-算法

  • 该算法从根对象(如全局变量、局部变量和静态字段)开始,递归遍历对象图,标记活动对象。标记阶段结束后,垃圾回收器会清理未被标记(不可达)的对象所占用的内存空间。
  • 产生问题: 存储空间不连续,碎片空间多
  • 一般应用于: 老年代对象

标记复制-算法

  • 该算法将内存空间分为两个相等的部分:"from"空间和 "to"空间。
  • 它从根对象开始,递归遍历对象图,将活动对象从 "from" 空间复制到 "to" 空间。复制阶段结束后,两个空间的角色互换,"from" 空间变为空。
  • 这种算法对于管理内存碎片化非常高效。
  • 产生问题: 多数对象存活,复制来复制去,将过多的消耗性能。同时浪费一半内存空间
  • 一般应用于: 新生代对象

标记整理-算法

  • 将存活对象进行移动当内存空间另外一端(整理),在清除存活对象边界以外的垃圾对象进行清除。(解决了浪费内存问题,让内存得到高效利用,同时解决了碎片化内存不连续的问题。)
  • 一般应用于: 老年代对象