01. JVM 内存模型
文章目录
1. Java 类的生命周期
一个 Java 类完整的生命周期会经历加载、连接、初始化、使用和卸载五个阶段。其中加载、连接和初始化过程由类加载器(classloader)完成,通过执行引擎运行在运行时数据区,使用完成后被垃圾回收器(GC,Garbage Collector)回收。图示如下:
2. JVM 内存模型(Run-Time Data Area)
Java 虚拟机(JVM,Java Virtual Machine)在运行程序时会把其自动管理的内存划分为五个区域:
- 程序计数器(PC,Program Counter)
- 虚拟机栈(JVM Stacks)
- 本地方法栈(Native Method Stacks)
- 堆(Heap)
- 方法区(Method Area)
图示如下:
线程独享以及线程共享的内存模型示意图:
2.1 程序计数器(PC,Program Counter)
(1)什么是程序计数器?
程序计数器是一块很小的内存空间,可以把它看作当前线程正在执行的字节码的行号指示器。也就是说,程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址。
(2)程序计数器的作用
- 字节码解释器通过改变程序计数器的值来选取吓一跳需要执行的字节码指令,从而实现代码的流程控制,如顺序执行、分支、循环、跳转、异常处理等。
- 在多线程情况下,程序计数器用来记录当前线程执行的位置,从而当线程被切换回来时能够知道该线程上次运行到哪儿了。
(3)程序计数器的特点
- 是一块很小的存储空间;
- 每个线程都有一个自己的程序计数器,是线程独享的内存空间;
- 生命周期随着线程的创建而创建,随着线程的结束而死亡。
面试题:为什么要记录当前线程的执行地址?
CPU 切换,要切换线程,来回执行。
2.2 虚拟机栈(VM Stacks)
虚拟机栈属于线程独享的数据区域,与线程同时创建,代表 Java 方法执行的内存模型。一个方法一个栈帧(Frame),每个方法执行时都会创建一个栈帧来存储方法的变量表、操作数栈、动态连接、返回值地址等信息。
栈帧(Frame)结构:
A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.
栈帧包括四部分的结构:
- Local Variables 局部变量表
- Operand Stacks 操作数栈
- Dynamic Linking 动态链接(多态时用)
- Return Address 返回值地址
2.3 本地方法栈(Native Method Stacks)
本地方法栈属于线程独享的数据区域,当线程访问本地方法的时候会用到。一般情况下,我们不用关心这块区域。
2.4 堆(Heap)
Java 堆属于线程共享的数据区域,它在虚拟机启动时创建,是 Java 虚拟机所管理的内存中最大的一块,主要用于存放实例对象,几乎所有的实例对象都在这里分配内存。Java 堆是垃圾收集器管理的主要区域,因此也叫做 GC 堆。
Heap 里关于垃圾回收的内存模型:
2.5 方法区(Method Area)
方法区属于线程共享的数据区域,又称非堆(Non-Heap),主要用于存储已被虚拟机加载的类信息、常量、静态变量等。当方法区无法满足内存分配需求时,将抛出 OOM(Out of Memory,内存溢出)异常。
在方法区中存在一个叫作运行时常量池(Runtime Constant Pool)的区域,它主要用于存放编译器生成的各种字面量和符号引用,这些内容在类加载后存放到运行时常量池中,以便后续使用。