JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略,保证了 JVM 的高效稳定运行
线程私有:程序计数器、虚拟机栈、本地方法栈
线程公有: 堆、方法区
线程私有的内存区域和线程的生命周期一致,会随着线程销毁而销毁。
程序计数器
Program Counter Register: Register命名 来自于PC的CPU寄存器,寄存器存储了指令的相关线程信息。这里的程序计数器是JVM 对物理PC寄存器的一种模拟
作用
存储将要执行代码的地址,由执行引擎去执行指令
为什么要用程序计数器
存储字节码指令地址有什么用?
cpu 在执行时,不会一直执行一个线程,是多个线程来回切换的,所以存储切换到这个线程时,程序从什么地方开始执行。
为什么它是线程私有?
在执行时是不停切换,交替执行的,中间会必然有中止、恢复出现,为了准确记录线程的当前执行字节码的地址,给每个线程分配一个程序计数器,这样就不会出现相互影响。
虚拟机栈
在每个线程创建时都会创建一个虚拟机栈,其内部放着一个个栈帧,对应着一次次的方法调用。
虚拟机栈主管java程序的运行,存放着方法的局部变量、部分结果以及参与方法的调用和返回
- 访问速度仅次于程序计数器
- JVM 对虚拟机栈只有两个操作 就是入栈和出栈
- 栈不存在垃圾回收问题
栈这里会有一个常见异常出现,一般虚拟机栈会有两种内存分配模式
- 固定内存
采用固定的虚拟机栈内存模式,在线程创建时内存已经是特立选定的,当线程请求分配的栈内存超过允许的最大内存时,会出现StackOverflowError
异常 - 动态
Java 虚拟机栈可以动态扩展,当尝试扩展时或者创建线程时分配栈内存时,内存不足,会抛出OutOfMemoryError
异常
可以通过参数-Xss
来设置线程的最大栈空间,决定了函数调用的最大深度。
存储单位
栈的最小存储单位是栈帧,栈帧是一个数据集,存储了方法执行过程中的各种数据信息。
- 局部变量表: 主要用于存储方法参数和定义在方法体内的局部变量
- 操作数栈(表达式栈)主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
- 动态链接:运行时常量池的方法引用
- 方法返回地址
- 一些附加信息
本地方法栈
本地方法接口
定义:一个 Java 调用非 Java 代码的接口
作用:
- java应用和外部环境交互
- 有时需要依赖一些底层系统的支持
本地方法栈
主要用于管理本地方法的调用,也和虚拟机栈运行原理差不多。
栈是运行时的单位,而堆是存储的单位。
堆
堆内存划分
堆主要用于存放对象实例,也是JVM 内存中占比最大的。
为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域
- 年轻代:存放新创建的对象和还没达到年龄的对象
- 老年代:被长时间使用的对象
- 元空间: 一些方法中的操作临时对象,JDK1.8 之前是占用 JVM 内存,JDK1.8 之后直接使用物理内存
分代是为了优化GC性能
年轻代
创建新对象的一般都会放入年轻代里面,年轻代被分成三个区域,伊甸园(Eden Memory)和两个幸存区(Survivor Memory,被称为from/to或s0/s1),默认比例是8:1:1
。年轻代有自己的GC,叫做Minor GC
在年轻代步骤:
- 创建的新对象首先放入Eden区,特别大的对象则会直接放入老年代中。
- 当Eden满了,会执行Minor GC,清除没有使用对象,还活着的对象则会被移入幸存区
- Minor GC 检查幸存者对象,并将它们移动到另一个幸存者空间。所以每次,一个幸存者空间总是空的
- 经过多次GC 还活着的对象会被放入老年代中
老年代
老年代放置的是在一次次小型GC中存活下来的对象,通常垃圾收集是指老年代存满了执行的垃圾收集,所以老年代的垃圾收集也叫主GC(Major GC),通常耗费的时间也是很长的。
元空间
不管是 JDK8 之前的永久代,还是 JDK8 及以后的元空间,都可以看作是 Java 虚拟机规范中方法区的实现。
堆内存的设置
- -Xms 用来表示堆的起始内存,等价于 -XX:InitialHeapSize
- -Xmx 用来表示堆的最大内存,等价于 -XX:MaxHeapSize
方法区
- 方法区(Method Area)与 Java 堆一样,是所有线程共享的内存区域。
- 虽然java虚拟机规范把方法区描述为堆逻辑的一部分,但是它有一个别名叫
非堆
- JVM 关闭后方法区即被释放