Java内存模型
2023-04-30 11:18:09 1 举报
AI智能生成
Java内存模型 思维导图
作者其他创作
大纲/内容
内存区块
分支主题
分支主题
实际上,工作内存、主内存都位于物理内存中
内存间交互操作
lock(锁定)
作用于主内存的变量,把一个变量标识为一条线程独占状态
unlock(解锁)
作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read(读取)
作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入)
作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
use(使用)
作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作
assign(赋值)
作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
store(存储)
作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
write(写入)
作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中
线程共享
方法区
存储多种信息
类信息
常量
静态变量
及时编译器编译后的代码
被描述为堆的逻辑部分
1.7之前是使用永久代来实现的方法区
容易遇到内存溢出问题
1.7中将字符串常量池移出了
当方法区无法满足内存分配需求时OOM
运行时常量池
用于存放class中的常量池数据
具备动态性
不要求常量一定是编译期产生
运行期间也可以放入新的常量
利用的最多的就是string.intern()
当无法申请到内存时OOM
通过 -XX:MaxPermSize 设置大小
堆
在虚拟机启动时创建
用来存放对象实例
使用分代回收算法
空间非连续
会抛出OOM
对象的内存布局
对象头
对象自身运行时数据
在32位和64位虚拟机中分别为32bit和64bit
官方名 Mark Word
与对象自身定义的数据无关的额外存储成本
根据对象状态复用自己的存储空间
类型指针
指向它的类元数据
虚拟机通过这个指针来确定对象是哪个类的实例
如果对象是数组
还有一块用于记录数组长度的数据
实例数据
程序代码中定义的各种类型的字段内容
包括父类继承的还是子类中定义的
对齐填充
由于hotspot VM的自动内存管理系统要求对象的起始地址必须是8字节的整数倍
当实例数据部分没有对齐时,使用对齐填充补充
通过-Xmx和-Xms控制大小
metaspace
1.8
不在虚拟机中分配,直接使用本地内存
大小只与配置和本地内存大小相关
线程隔离(私有)
程序计数器
记录线程锁执行字节码的行号
java方法记录的是正在执行的虚拟机字节码指令地址
native方法的话,计数器值为空
唯一一个没有OOM的区域
虚拟机栈(VM栈)
生命周期与线程相同
保存线程运行状态
描述的是Java方法执行的内存模型
最小单位是栈帧
局部变量表
存放可知的基本数据类型
对象引用地址
空间以32位为粒度
超过64位的数据占用两个变量空间
double
long
操作数栈
动态链接
返回地址
非连续内存空间
会抛出异常
OOM
当栈扩展无法申请到做够的内存时
StackOverFlow
当线程请求的栈深度超过虚拟机栈深度
本地方法栈
为虚拟机提供Native方法服务
同步原语
lock 锁
锁的释放 - 获取建立的happens-before关系
锁释放和获取的内存语义
锁内存语义的实现
concurrent包的实现
volatile
特性
可见性:任意线程都volatile最新值都可见
原子性:对任意单个volatile变量的读/写具有原子性
内存语义
当写一个volatile变量时,JMM会吧该线程对应的本地内存中的共享变量刷新到主内存中
volatile内存语义的实现
JSR-133 增强volatile的内存语义
final
final域的重排序规则
在构造函数内对一个final域的写入,与将final对象赋值给引用变量,这两个操作之间不能重排序
初次读取final域的对象引用,与初次读取final域对象,这两个操作之间不能重排序
写final域的重排序规则
规则
JMM禁止编译器把final域的写重排序到构造函数之外
JMM会在final域的写之后,构造函数return之前,插入一个storestore屏障。禁止final域的写重排序到构造函数之外
确保
在对象引用为任意线程可用之前,对象的final域已经被正确初始化,普通域不具有这个保障
读final域的重排序规则
确保
在读一个final域之前,一定先读该final域的对象的引用
如果final域是引用类型
在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把构造的对象的引用赋值给一个引用变量,这两个操作之间不能重排序
为什么final引用不能从构造函数内“逸出”
final语义在处理器中的实现
JSR133 为什么要增强final的语义
只要对象是正确构造的(被构造对象的引用在构造函数中没有溢出),那么不需要使用同步(指lock与volatile的使用)就可以保证任意函数都可以看到final域在构造函数内被初始化后的值
重排序
数据依赖性
as-if-serial语义
不管怎么重排序,单线程程序的执行结果不能发生改变
程序顺序规则
重排序对多线程的影响
禁止重排序
内存屏障
分支主题
顺序一致性
数据竞争与顺序一致性保证
顺序一致性内存模型
一个线程中的所有操作必须按照程序的顺序来执行
(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见
同步程序的顺序一致性
未同步程序的执行特性
顺序一致性模型保证单线程内的操作会按程序的顺序执行,而JMM不保证单线程内的操作会按程序的顺序执行
顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不保证所有线程能看到一致的操作执行顺序
CPU缓存
分支主题
缓存结构
L1最接近CPU,容量最小,速度最快 数据缓存 L1d Cache, + 指令缓存L1i Cache
L2 Cache更大一些,如256K,速度稍慢,一般每个核都要一个独立的L2 Cache
L3 Cache多核共用
缓存一致性
MESI协议
M(Modified):这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中
E(Exclusive):这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中
S(Shared):这行数据有效,数据和内存中的数据一致,数据存在于很多Cache中
I(Invalid):这行数据无效
缓存行 Cache line
64字节
缓存最小操作单位
如果没有利用好缓存行,可能会遇到性能问题
查询缓存行大小
$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
伪共享(False Sharing)
原因
多线程时,如需修改“共享同一缓存行变量”,就会无意中影响彼此的性能(互斥)
解决原理
单个数据填充满一个Cache Line(空间换时间)
自动补齐缓存行
解决方案
1.7
// jdk7以下使用此方法13;public final static class VolatileLong13;{13; public long p1, p2, p3, p4, p5, p6, p7; // cache line padding13; public volatile long value = 0L;13; public long p8, p9, p10, p11, p12, p13, p14; // cache line padding13;}
1.8
@sun.misc.Contended13;public final static class VolatileLong {13; public volatile long value = 0L;13; //public long p1, p2, p3, p4, p5, p6;13;}13;13;jvm启动时设置 -XX:RestrictContended
缓存失效
第一次访问数据,在cache中根本不存在,所以cache miss,可通过 prefetch 解决
数据预取
cache冲突,通过补齐解决(伪共享的产生)
cache满,一般需要减少操作的数据大小,尽量按数据的物理顺序访问数据
基础
线程通信
消息传递
共享内存
线程同步
显式
隐式
happens-before
重排序
0 条评论
下一页