JVM--堆

JVM–堆

背景

  • JVM–堆

  • 博主以黑马JVM进行学习

定义

  • 通过 new 关键字创建的对象实例和数组,都会占用堆内存(补充:对象的元数据存在元空间,而非堆)
  • 堆是 JVM 运行时数据区中最大的一块内存区域,也是 GC(垃圾回收)的核心区域。

特点

  • 线程共享:整个 JVM 进程中只有一个堆,所有线程都能访问堆中的对象,因此堆中对象的读写需要考虑线程安全(比如用 synchronizedAtomic 等);
  • 有垃圾回收机制:堆是 GC 唯一的核心工作区域(虚拟机栈 / 本地方法栈无需 GC,程序计数器无 GC 必要),堆内存会被划分为不同区域(年轻代、老年代、元空间),不同区域采用不同的 GC 策略;
  • 可动态扩展:堆内存大小可通过 JVM 参数调整(默认会根据物理内存自动调整),也可设置固定大小。

堆的内存溢出

  • 堆内存溢出错误:OutOfMemoryError:Java heap space(最常见的 OOM 类型)
  • 堆溢出原因:
    • 内存泄漏:对象不再使用但 GC 无法回收(比如静态集合长期持有对象引用、未关闭的资源连接);
    • 内存溢出:正常业务创建的对象过多 / 过大,超出堆内存上限(比如一次性加载百万级数据到内存);
  • 核心参数:
    • -Xms:堆内存初始大小(如 -Xms200m,建议和 -Xmx 设为相同值,避免运行时动态扩容消耗性能);
    • -Xmx:堆内存最大上限(如 -Xmx512m,超出该值则抛出堆 OOM);

堆内存诊断

  • jps工具:查看当前系统中有哪些java进程(定位目标进程 ID)

    • jps # 输出示例:12345 YourMainClass(12345是进程ID)
      
      1
      2
      3
      4
      5
      6
      7

      * jmap工具:查看堆内存占用情况 jmap - heap 进程id

      * ```bash
      jmap -heap 进程ID # 查看堆的整体配置和使用情况(年轻代、老年代占用率)
      jmap -histo 进程ID # 查看堆中对象的数量、大小(按内存占用排序,定位大对象)
      jmap -dump:format=b,file=heapdump.hprof 进程ID # 导出堆快照文件(用于后续分析)
  • jconsole工具:图形界面的,多功能的监测工具,可以连续监测(JDK 自带,无需额外安装)

    • 启动方式:命令行输入 jconsole,选择目标 Java 进程即可连接;
    • 核心功能:实时监测堆内存变化、GC 次数 / 耗时、线程状态等,支持连续监测。
  • 案例:垃圾回收后,内存占用仍然很高

    • 场景:程序运行一段时间后,GC 频繁触发,但回收后堆内存占用仍居高不下(大概率是内存泄漏)

    • 使用jvisualvm工具(JDK 自带,可视化分析神器)

      • 可视化的虚拟机
      • 堆Dump功能:导出堆快照文件(.hprof);

总结

  • 堆是 JVM 最大的线程共享内存区域,new 创建的对象实例 / 数组都存于堆中,需考虑线程安全;
  • 堆是 GC 的核心区域,堆内存溢出会抛出 OutOfMemoryError: Java heap space,可通过 -Xms/-Xmx 调整堆大小,核心解决思路是排查内存泄漏 / 减少大对象创建;
  • 堆内存诊断常用工具:jps(查进程)→ jmap(查静态内存)→ jconsole(实时监测)→ jvisualvm(堆 Dump 分析),其中 jvisualvm 是排查内存泄漏的核心工具。

补充:堆的分代模型

  • 堆内存内部会划分为:

    • 年轻代(Young Generation):存储新创建的对象,GC 频率高(Minor GC),分为 Eden 区、S0/S1(Survivor)区;

    • 老年代(Old Generation):存储存活时间长的对象,GC 频率低(Major GC/Full GC);

    • 元空间(Metaspace):JDK 8 后取代永久代,存储类的元数据(不属于堆,但常和堆一起讨论);

      这是 GC 策略的基础,理解分代模型能更好地解释 “为何 GC 后内存仍高”(比如老年代对象无法被 Minor GC 回收)。