【JAVA】JVM中的内存管理详解

JVM中的内存管理详解

JVM的内存管理是确保Java程序高效运行的重要机制。它自动管理内存分配和回收,帮助开发者避免内存泄漏和其他内存管理问题。JVM的内存分为多个区域,各自负责不同的任务。

1. JVM内存结构

JVM的内存主要分为以下几个区域:

  • 方法区(Method Area):存储已加载的类信息、常量、静态变量和方法代码。方法区属于线程共享区域。
  • 堆区(Heap Area):用于存储所有的对象实例和数组,是内存管理的核心区域,所有线程共享此区域。
  • 虚拟机栈(JVM Stack):每个线程有一个独立的虚拟机栈,存储局部变量、操作数栈、动态链接、方法出口等。每次方法调用都会创建一个栈帧用于存储这些数据。
  • 程序计数器(Program Counter Register):每个线程有一个独立的程序计数器,用于记录当前正在执行的字节码指令的地址。
  • 本地方法栈(Native Method Stack):与虚拟机栈类似,只不过它为本地方法服务。

2. 堆内存的分代模型

JVM的堆内存根据对象的生命周期划分为两个主要区域:

  • 新生代(Young Generation):包括Eden区和两个Survivor区。大多数对象在这里被分配,Eden区是对象创建的地方,两个Survivor区用于对象的存活与回收。
  • 老年代(Old Generation):存储生命周期较长的对象。新生代经过多次垃圾回收仍然存活的对象会被移到老年代。

3. 垃圾回收机制(Garbage Collection, GC)

垃圾回收是JVM自动管理内存的机制,负责回收不再使用的对象。JVM主要使用以下几种垃圾回收算法:

  • 标记-清除算法(Mark-Sweep):标记所有需要回收的对象,并在标记完成后进行清除。
  • 复制算法(Copying):将存活对象从一个区域复制到另一个区域,非存活对象的空间则被清除。
  • 标记-整理算法(Mark-Compact):标记存活对象并移动它们,使所有存活对象都集中在一起,之后清理掉空闲的空间。

4. 示例代码:手动触发垃圾回收

在Java中,可以通过调用System.gc()方法手动建议JVM进行垃圾回收,虽然JVM不保证立刻执行垃圾回收,但这是一个常用的触发方式。

示例代码:

public class GarbageCollectionExample { // 定义一个类GarbageCollectionExample
    public static void main(String[] args) {
        MyClass myClass1 = new MyClass(); // 创建第一个对象
        MyClass myClass2 = new MyClass(); // 创建第二个对象
        myClass1 = null; // 将myClass1置为null,使其对象可被GC回收
        myClass2 = null; // 将myClass2置为null,使其对象可被GC回收
        System.gc(); // 手动调用垃圾回收器
    }
}

class MyClass {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("对象被回收");
    }
}

中文注释:

public class GarbageCollectionExample { // 定义一个类GarbageCollectionExample
    public static void main(String[] args) {
        MyClass myClass1 = new MyClass(); // 创建第一个对象
        MyClass myClass2 = new MyClass(); // 创建第二个对象
        myClass1 = null; // 将myClass1置为null,使其对象可被GC回收
        myClass2 = null; // 将myClass2置为null,使其对象可被GC回收
        System.gc(); // 手动调用垃圾回收器
    }
}

class MyClass {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("对象被回收"); // 在对象被回收时输出提示信息
    }
}

5. 垃圾回收器(Garbage Collector)

JVM提供了多种垃圾回收器,可以根据应用程序的需求选择合适的回收器:

  • Serial GC:适用于单线程环境的垃圾回收器,暂停时间较长。
  • Parallel GC:使用多线程进行垃圾回收,适用于多处理器环境。
  • CMS(Concurrent Mark-Sweep)GC:并发标记-清除垃圾回收器,减少停顿时间。
  • G1(Garbage-First)GC:面向大内存的垃圾回收器,适合低停顿时间需求的应用程序。

6. 源码解析

在JVM的源码中,垃圾回收器的实现和内存管理是非常复杂的部分。以下是java.lang.Runtime类中的gc()方法的简化版,它调用了垃圾回收器:

源码解析:

public class Runtime {
    private static final Runtime currentRuntime = new Runtime();

    // 返回当前Runtime实例
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    // 手动触发GC
    public void gc() {
        // 触发垃圾回收
        System.gc();
    }
}

中文注释:

public class Runtime {
    private static final Runtime currentRuntime = new Runtime(); // 单例模式,保存Runtime实例

    public static Runtime getRuntime() {
        return currentRuntime; // 返回当前Runtime实例
    }

    public void gc() {
        System.gc(); // 手动触发垃圾回收
    }
}

面试题

1、默认使用哪个垃圾回收器

在Java中,JVM默认使用的垃圾回收器取决于所使用的JVM版本和运行时环境。以下是不同版本的JVM默认使用的垃圾回收器:

  1. JDK 8及之前版本:
    • 客户端模式(Client VM): 默认使用 Serial GC。这是一个单线程的垃圾回收器,适用于小型应用程序,停顿时间较长。
    • 服务器模式(Server VM): 默认使用 Parallel GC。这是一个多线程垃圾回收器,适用于多处理器环境,能够缩短垃圾回收的暂停时间。
  2. JDK 9及之后版本:
    • 默认使用 G1 GC(Garbage-First Garbage Collector)。G1 GC旨在替代之前的Parallel GC,特别适合处理大堆内存,能够在可控的停顿时间内高效地进行垃圾回收。

默认GC的选择依据

在JDK 8之前,客户端模式和服务器模式的选择通常是根据硬件配置自动决定的。例如,JVM在检测到多个CPU核心和大量内存时,会自动选择服务器模式。服务器模式默认使用Parallel GC,因为它在多核环境下具有更好的并行性能。
从JDK 9开始,JVM的默认垃圾回收器变更为G1 GC,因为它在大多数应用场景下都能提供更好的性能和平衡的停顿时间。
如何查看和更改垃圾回收器
你可以通过以下命令行参数来查看或指定JVM使用的垃圾回收器:

  • 查看当前使用的垃圾回收器:
java -XX:+PrintCommandLineFlags -version
  • 指定使用G1 GC:
java -XX:+UseG1GC MyApplication
  • 指定使用Parallel GC:
java -XX:+UseParallelGC MyApplication
  • 指定使用Serial GC:
java -XX:+UseSerialGC MyApplication
  • 指定使用CMS GC(在JDK 14及之后被废弃):
java -XX:+UseConcMarkSweepGC MyApplication

2、垃圾回收器分别使用哪些垃圾回收机制

在JVM中,垃圾回收器(GC)使用了不同的回收机制来管理内存,以下是主要垃圾回收器及其使用的回收机制:

1. Serial GC

- 回收机制: 标记-清除(Mark-Sweep) 和 标记-整理(Mark-Compact)

  • 工作原理:
    • 新生代:使用 复制算法(Copying)。它将存活的对象从Eden区和一个Survivor区复制到另一个 Survivor区,非存活对象则被清理。
    • 老年代:使用 标记-整理算法。首先标记出存活对象,然后将存活对象压缩到堆的一端,清理掉剩余空间。这是一个单线程的垃圾回收器,每次回收都会暂停应用程序的所有线程(即Stop-the-World,STW)。
2. Parallel GC(也称为Throughput GC)
  • 回收机制: 标记-清除(Mark-Sweep) 和 标记-整理(Mark-Compact)
  • 工作原理:
    • 新生代:使用 复制算法(Copying)。和Serial GC类似,但使用多线程并行回收。
    • 老年代:使用 标记-整理算法。它与Serial GC类似,但使用多线程进行并行回收,适用于多处理器环境,能够缩短垃圾回收的停顿时间。
3. CMS GC(Concurrent Mark-Sweep GC,已在JDK 14中被弃用)
  • 回收机制: 标记-清除(Mark-Sweep)
  • 工作原理:
    • 新生代:使用 复制算法(Copying),与Parallel GC类似。
    • 老年代:使用 并发标记-清除算法。CMS GC的目标是最小化停顿时间(STW)。它在标记阶段与应用程序线程并发工作,尽量减少对应用程序的影响。它的回收过程分为以下几个阶段:
      1. 初始标记(Initial Mark):标记GC Roots可达的对象,这个过程需要暂停所有应用线程(STW)。
      2. 并发标记(Concurrent Mark):在应用程序运行时标记所有GC Roots可达的对象。
      3. 重新标记(Remark):修正并发标记阶段应用程序导致的标记变化,需要暂停所有应用线程(STW)。
      4. 并发清除(Concurrent Sweep):并发清理未被标记的对象。
  • 缺点: CMS GC在回收老年代时不进行对象整理,可能会导致内存碎片化。
4. G1 GC(Garbage-First GC)
  • 回收机制: 标记-整理(Mark-Compact) 和 部分区域复制(Region-Based Copying)
  • 工作原理:
    • G1 GC将堆内存划分为多个大小相等的区域(Region),这些区域可以作为新生代或老年代使用。G1的主要目标是提供可预测的停顿时间。
    • 新生代:G1使用 复制算法,将存活对象从Eden区和Survivor区复制到新的Survivor区或老年代。
    • 老年代:G1使用 标记-整理算法。老年代的GC是通过多次全局标记-整理操作完成的。G1首先标记存活的对象,然后整理(即压缩)老年代内存,将存活对象移动到一端,释放出连续的内存空间。
    • G1会优先回收垃圾最多的区域,因此得名“Garbage-First”。
5. ZGC(Z Garbage Collector)
  • 回收机制: 标记-整理(Mark-Compact) 和 Region-Based
  • 工作原理:
    • ZGC是一个低延迟垃圾回收器,目标是将GC停顿时间保持在几毫秒以内,即使在非常大的堆内存下(比如几百GB甚至TB)。
    • ZGC通过并发标记、并发重定位和并发清除来完成回收,并使用染色指针(Colored Pointers)和读屏障(Load Barriers)来精确跟踪对象的引用状态。
    • 它会在并发标记过程中标记存活的对象,在并发重定位阶段对存活对象进行压缩和整理。
6. Shenandoah GC
  • 回收机制: 标记-整理(Mark-Compact) 和 Region-Based
  • 工作原理:
    • Shenandoah GC与ZGC类似,也是低延迟GC,目的是将GC停顿时间保持在可控范围内。
    • 它的并发过程包括并发标记、并发整理、并发回收和并发引用更新等多个阶段,减少应用程序的停顿时间。
  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值