对于 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 虚拟机栈合二为一。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError
和 OutOfMemoryError
两种错误。
堆
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 及以后方法区的实现变成了元空间。