对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。


线程私有的:

  • 程序计数器

  • 虚拟机栈

  • 本地方法栈

线程共享的:

  • 方法区

  • 直接内存 (非运行时数据区的一部分)

JVM 为了运行 Java 程序,把内存划成了几个不同的区域,每块都承担不同的任务:

1️⃣ 程序计数器(Program Counter)

  • 是每个线程都有的小型内存空间

  • 记录当前线程执行的字节码的地址。

  • 可以理解为“正在执行哪一行代码”。

👉 多线程靠它来切换上下文,不然早打架了!


2️⃣ Java 虚拟机栈(JVM Stack)

  • 每个线程有一个,用来存储方法调用栈帧

  • 每个方法运行时都会创建栈帧,里面包括:

    • 局部变量表

    • 操作数栈

    • 方法出口信息等

💥 方法执行完就弹栈,轻轻松松不留痕。


3️⃣ 本地方法栈(Native Method Stack)

  • 类似于虚拟机栈,但执行的是 Native 方法(用 C 写的)

  • 比如调用 System.arraycopy() 这类本地方法。


4️⃣ 堆(Heap)

  • JVM 中最大的一块内存区域

  • 所有的对象实例都在这!(包括数组)

  • 被 GC 管理回收!

🍃 新生代(Young)+ 老年代(Old) = 常说的 Java 堆
→ 新生代分 Eden、Survivor,从小可爱慢慢熬成老大哥。


5️⃣ 方法区(Method Area)

  • 也叫“元空间”(MetaSpace)

  • 存储类的信息(类结构、常量池、静态变量、JIT 编译后的代码等)

结构大致图:

┌────────────────────┐

│ 程序计数器(每线程) │

├────────────────────┤

│ JVM栈(每线程) │

├────────────────────┤

│ 本地方法栈(每线程) │

├────────────────────┤

│ 堆(共享) │ ← new 出来的对象都在这

├────────────────────┤

│ 方法区(共享) │ ← 类信息、静态常量等

└────────────────────┘

本地方法栈

和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。

方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowErrorOutOfMemoryError 两种错误。

Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

Java 世界中“几乎”所有的对象都在堆中分配,但是,随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。

Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:Eden、Survivor、Old 等空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。


方法区

方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。

《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,方法区到底要如何实现那就是虚拟机自己要考虑的事情了。也就是说,在不同的虚拟机实现上,方法区的实现是不同的。

当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

方法区和永久代以及元空间是什么关系呢? 方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。


青い空