JVM - 运行时数据区

JVM 解密 —— 运行时数据区

1. 核心理论:JVM 是如何管理内存的?

当一个 Java 程序运行时,JVM 会在内存中划分出不同的区域来存储各种数据。这些区域统称为运行时数据区 (Runtime Data Areas)。根据《Java虚拟机规范》,它主要分为两大类:线程共享的和线程私有的。

JVM 运行时数据区
├── 线程私有 (每个线程都有一份)
│   ├── 程序计数器 (Program Counter Register)
│   ├── Java 虚拟机栈 (Java Virtual Machine Stack)
│   └── 本地方法栈 (Native Method Stack)
│
└── 线程共享 (所有线程共享一份)
    ├── Java 堆 (Java Heap)
    └── 方法区 (Method Area)

2. 深度剖析:五大内存区域

2.1 线程私有区域

这部分区域的生命周期与线程相同,随线程的创建而创建,随线程的销毁而销毁。

  1. 程序计数器 (PC Register)

    • 作用: 是一块很小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。JVM 通过改变这个计数器的值来选取下一条需要执行的字节码指令。
    • 特点: 它是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError(堆内存溢出错误) 情况的区域。
  2. Java 虚拟机栈 (JVM Stack)

    • 作用: 存放栈帧 (Stack Frame)。每个方法在执行时,JVM 都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。我们常说的“栈内存”主要就是指这里。
    • 局部变量表: 存放了编译期可知的各种基本数据类型(boolean, byte, char, short, int, float, long, double)、对象引用(reference 类型)。
    • 异常: 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError(栈溢出错误);如果栈可以动态扩展,但在扩展时无法申请到足够的内存,会抛出 OutOfMemoryError(堆内存溢出错误)
  3. 本地方法栈 (Native Method Stack)

    • 作用: 与虚拟机栈非常相似,区别在于它为虚拟机使用到的本地方法 (Native Method) 服务。本地方法通常是由 C 或 C++ 实现的。

2.2 线程共享区域

这部分区域随虚拟机的启动而创建,是所有线程共享的,也是垃圾回收(GC)发生的主要区域。

  1. Java 堆 (Java Heap)

    • 作用: 是 JVM 所管理的内存中最大的一块。它的唯一目的就是存放对象实例。我们 new 出来的几乎所有对象,都在这里分配内存。
    • 特点: 是垃圾收集器管理的主要区域,因此也被称作“GC堆”。从内存回收的角度看,现在的收集器基本都采用分代收集算法,所以 Java 堆还可以细分为:新生代老年代
    • 异常: 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError(堆内存溢出错误)
  2. 方法区 (Method Area)

    • 作用: 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    • 演进:从永久代 (PermGen) 到元空间 (Metaspace)

      • JDK 1.7 及之前: 方法区由永久代实现。永久代是堆的一部分,有固定的内存大小上限(-XX:MaxPermSize),在动态加载大量类的场景下,非常容易导致 OutOfMemoryError(堆内存溢出错误): PermGen space
      • JDK 1.8 及之后: 永久代被彻底移除,取而代之的是元空间。元空间使用的是本地内存 (Native Memory),不再占用堆内存,其大小理论上只受限于机器的总物理内存,从而极大地降低了因类信息过多而导致 OOM 的风险。
    • JDK 8 迁移后,数据都去哪儿了?

      • 类元信息 (类的结构、方法、字段等): 从永久代迁移到了元空间
      • 字符串常量池: 从永久代迁移到了 Java 堆中。
      • 静态变量 (static variables): 从永久代迁移到了 Java 堆中,与该类的 java.lang.Class 对象存放在一起。

3. 生活中的例子:大学图书馆学习

  • JVM: 整座大学

  • 线程: 每个来上自习的学生

  • Java 虚拟机栈 (线程私有): 每个学生自己桌上的草稿纸。你在算微积分(执行方法A)时,在草稿纸上写满了公式(局部变量);接着你又算物理题(执行方法B),又用了一张新的草稿纸。算完物理题,就把这张草稿纸扔了(方法B出栈)。你的草稿纸别人不能用。

  • 程序计数器 (线程私有): 你正在阅读的书本上,用来标记当前读到哪一页的书签。你知道接下来该读哪一页。

  • Java 堆 (线程共享): 图书馆的中央书库。所有“书籍”(对象实例)都存放在这里。所有学生都可以从这里借阅(引用)书籍。

  • 方法区 (线程共享): 图书馆的索引区/卡片目录。这里存放着所有书籍的“元信息”(类信息),比如书名、作者、分类号、有多少章节等。所有学生都共享这份目录来查找他们需要的书。

posted @ 2026-01-21 16:05  我是刘瘦瘦  阅读(0)  评论(0)    收藏  举报