INDEX
§1 JVM 内存模型简介
上面内存组成中,堆和方法区(JDK1.8后称为元空间)为线程共享,其余均为线程独占。
另一说 JVM 内存由7部分组成,这是因为单独计算了 运行时常量池(它是方法区的一部分) 并额外计算了直接内存(但其实它不是JVM内部的一部分)
§1.1 程序计数器(Program Counter Register)
程序计数器是线程独立的
用于记录当前线程下一条需要被执行的指令
§1.2 虚拟机栈(Java Stack)
也称java 栈、线程栈,
线程独立,生命周期同线程
包含多个栈帧:方法执行时为其分配的栈空间
默认大小约 1M,与操作系统主机配置有关
用于存储下面几种数据:
- 局部变量表
- 操作数栈
- 动态链接
- 方法出入口
示例代码
public static int a(){
int a = 1;
return a;
}
public static void main(){
System.Out.Println(a());
}
main 方法调用 a 方法时,是通过动态链接找到的 a 方法
a 方法中对局部变量 a 进行赋值时,会先将 1 压入操作数栈,然后在局部变量表中开辟空间存放变量 a ,再从操作数栈弹栈赋值给变量 a
执行结束后,通过方法出入口记录的地址找到 main 方法调用 a 方法的位置进行返回并是不接下来要执行的程序
§1.3 本地方法栈(Native Method Stack)
Native 方法使用的内存空间
§1.4 堆(Heap)
所有线程共享
java中所有对象实例以及数组都存放在堆中
是垃圾收集器主要管理的区域
默认大小约 1/64(初始) 到 1/4(最大) 物理内存
组成
- 新生代
- 伊甸
- TLAB:Thread Allocation buffer
jvm 在 eden 中给每一个线程划分的一块缓冲区,用于加速对象空间分配
TLAB 是线程独占的
对象优先在此空间中分配,若对象过大,依然分配与 Eden 的共享内存中
- TLAB:Thread Allocation buffer
- 幸存区 0(FROM)
- 幸存区 1 (TO)
- 伊甸
- 老年代
§1.5 元空间
JDK 1.8 以后叫 元空间,1.8 以前叫 永久代。
所有线程共享
用于保存
- 类信息
- 常量
- 静态变量
- 即时编译器编译后的代码缓存
常量池
常量池有 3 种
- 静态常量池
- 运行时常量池:其实就是静态常量池的内容被运行时加载到内存后的状态
- 字符串常量池
常量、静态变量 的对象 在堆里
从下面案例可以看到静态变量与常量的对象时在堆里的,但其引用应该是在方法区(否则没法解释)
public class MetaspaceBoomDemo {
static byte[] a = new byte[10 * 1024*1024];//静态变量 10M
// public static final byte[] a = new byte[10 * 1024*1024];//常量 10M
public static void main(String[] args) {
System.out.println(new MetaspaceBoomDemo());
}
}
堆 3M ,元空间 30M:-Xms3m -Xmx3m -XX:MetaspaceSize=30m
,爆炸了
堆 30M ,元空间 3M:-Xms30m -Xmx30m -XX:MetaspaceSize=3m
,没事
准确的讲,三大常量池、类信息的内存分布其实如下图
§2 JVM 的直接内存
JVM 的直接内存,是 JVM 直接使用的 JVM 之外的内存
默认大小约 1/4(最大) 物理内存(与 JVM 堆一致)
直接内存的作用主要包括
- JDK 8 及之后版本的 Metaspace 是直接保存在直接内存的
但要注意 JDK 6 及以前的 字符串常量池 是在永久代(JDK 7 及以前的方法区)中的,从 7 开始放到 堆 里面了 - NIO 在使用过程中可以通过
ByteBuffer.allocteDirect(capability)
在直接内存中划分区域使用
此方式因为避免了在 Java 堆 和 Native 堆 之间来回复制数据,所以相对较快
但是若不断分配直接内存,JVM 堆内存使用较少,JVM 就不会触发 GC ,当直接内存先分配满还没 GC 时,会出现 OOM:Direct buffer memory
§3 JMM 内存模型
JMM 是 java 内存模型:java Memory Model ≠ JVM 内存模型
JMM 本身是一个抽象概念,并不真实存在
JMM 描述了一组规则规范,通过它定义了程序中各个变量的访问方式
上述变量包括成员变量、静态变量和集合中的元素
JMM 内存模型
示意图
概念
主内存
也可以理解为 共享内存
JMM 将大部分变量都视为存储在 主内存 中,这些变量都可能被多线程同步访问操作
本地内存
也可以理解为 工作内存,对线程而言是私有的
工作内存是相互隔离的,其互相访问需要通过主内存
线程操作共享内存中变量的流程
- 从主内存复制变量进线程的工作内存中
- 操作变量副本
- 操作结果同步回主内存
- 线程解锁前,必须将共享变量的值刷新会主内存
- 线程加锁前,必须读取主内存的值到自己的工作内存
- 加锁和解锁是对于同一把锁
JMM 工作流程
- read
作用于主内存
从主内存将值传输到工作内存 - load
作用于工作内存
将 read 到的值存入工作内存变量副本中 - use
作用于工作内存
将工作内存变量副本的值传递给 CPU
每次使用到变量都会执行此操作 - assign
作用于工作内存
将 CPU 处理后的结果赋值给工作内存变量副本
每次给变量赋值时,都会执行此操作 - store
作用于工作内存,将工作内存变量副本的值写回主内存(值传递给主内存) - write
作用于主内存,将 store 传输的值写回主内存变量 - lock
作用于主内存
为防止线程安全问题,将变量标记为线程独占
只锁了写变量的过程 - unlock
作用于主内存
释放锁定状态的变量