授课语音

Java堆和垃圾回收器

1. 介绍

Java的堆内存是JVM内存管理的重要对象,主要用于动态分配对象和数组。堆内存的结构可以分为几个区域:

  • 新生代:新生代主要用于存放新创建的对象。新生代又分为:

    • Eden区:所有新对象首先分配在这个区域。当Eden区满时,会触发一次MinorGC,速度较快,回收频率也较高。
    • Survivor区:用于存储从Eden区复制过来的存活对象,分为S0和S1两个区域,用于进行对象的存活检查和存放。
  • 老年代:当对象在新生代经历多次垃圾回收后仍然存活,它们会被移动到老年代。某些大对象可能会直接分配到老年代。当老年代满时,会触发MajorGC或FullGC,速度较慢,回收频率低。

  • 永久代:在Java8之前,永久代用于存储类的元数据。但由于永久代大小固定,容易出现OOM问题,因此在Java8之后被元空间取代。

  • 元空间:元空间用于存储类的元数据,使用本地内存,不受堆大小的限制,包括类结构、方法和字段等信息。

图示 Image___1397757896010610285===f5ef1bb0233489b09b561fccc24e0e5f===2_1.png___

垃圾回收算法

常见的垃圾回收算法包括:

  • 标记-清除算法:分为标记和清除两个阶段。首先,从根节点(如静态变量)开始,标记所有可达的对象,然后清除那些不可达对象的内存。虽然实现简单,但可能会产生内存碎片。

  • 复制算法:将内存分为两个区域,将存活的对象从当前区域复制到另一个区域,然后清理当前区域的对象。这种方法减少了内存碎片,但会浪费一些空间。

  • 标记-整理算法:类似于标记-清除,但在清除阶段将存活的对象移到一起,减少内存碎片,这需要额外的时间开销。

  • 分代收集算法:新生代对象生命周期短,使用复制算法;老年代对象生命周期长,使用标记-整理算法。这种策略提高了垃圾回收效率。

  • 并发标记算法:使用三色标记法,将对象分为白色(未被访问)、灰色(已被访问但引用的对象还未被访问)和黑色(已被访问且引用的对象也被访问)。标记过程分为初识标记、并发标记和最终标记三个步骤。

图示 Image___1397757896012532825===759fad544ce8945f36fcae0a51857166===2_2.png___

垃圾回收器

不同的垃圾回收器使用不同的算法和策略:

  • Serial GC:使用复制算法,单线程执行,垃圾回收过程会暂停应用线程。

  • Parallel GC:结合使用复制和标记-整理算法,多线程执行,减少垃圾回收时间,但同样会导致应用线程暂停。

  • CMS GC:使用标记-清除和并发标记算法,通过并发标记减少应用线程的停顿时间。

  • G1 GC:采用分代收集算法,将堆分为多个区域,优先回收垃圾最多的区域,旨在平衡应用线程的停顿时间和吞吐量,是Java8之后的默认回收器。

  • ZGC:基于并发标记算法的低延迟垃圾回收器,应用线程的停顿时间可预测。从Java15开始,可以通过-XX:+UseZGC切换使用。

2. 代码案例

下面是一个示例代码,通过创建大量对象并使用System.gc()来触发垃圾回收。通过添加-XX:+PrintGCDetails参数,可以在控制台中输出垃圾回收的详细信息,以便分析垃圾回收的行为。还可以使用不同的垃圾回收器来观察行为变化,比如使用-XX:+UseG1GC

public class GCDemo {
    private static final int _1MB = 1024 * 1024; // 定义1MB的常量

    static class MemoryObject {
        private byte[] memory = new byte[2 * _1MB]; // 创建一个2MB的对象
    }

    public static void main(String[] args) {
        // 1. 创建大对象并观察垃圾回收行为
        MemoryObject[] objects = new MemoryObject[10]; // 创建10个MemoryObject数组
        for (int i = 0; i < objects.length; i++) {
            objects[i] = new MemoryObject(); // 每次循环创建一个2MB的对象
            System.out.println("创建对象 " + i);
            // 2. 强制垃圾回收
            if (i % 2 == 0) { // 每创建两个对象强制垃圾回收一次
                System.gc(); // 调用垃圾回收
                System.out.println("在创建对象 " + i + " 后强制进行垃圾回收");
            }
        }

        // 3. 程序结束前进行最终的强制垃圾回收
        System.gc();
        System.out.println("在退出前强制进行最终的垃圾回收");
    }
}

这段代码首先定义了一个MemoryObject类,其中包含一个2MB的字节数组。在main方法中创建了一个MemoryObject数组,并在每次创建对象后强制触发垃圾回收。通过控制台输出,可以观察到不同垃圾回收策略对内存的影响,以及垃圾回收的时机和频率。

通过以上内容,相信大家对Java的堆内存和垃圾回收器有了更深入的理解。

去1:1私密咨询

系列课程: