深入理解Java虚拟机-JAVA内存模型与线程
Java内存模型(JMM)
JMM 的核心概念
主内存与工作内存:
- 主内存(Main Memory)是所有线程共享的内存区域,存放着所有变量的值
- 每个线程都有自己的 工作内存(Working Memory),它是该线程的私有内存区域。线程操作共享变量时,先从主内存将变量拷贝到工作内存中,然后对工作内存中的变量进行修改,最后再将修改结果写回主内存
共享变量:
- JMM共享变量是多个线程可以访问的变量。通常是
static变量或者实例变量。局部变量是线程私有的,不受 JMM 的影响
内存屏障(Memory Barriers) :
- 内存屏障是指 CPU 或者编译器用来保证操作顺序的一种机制。它通过禁止指令重排,确保某些操作在执行时的顺序
变量的可见性、原子性与有序性:
- 可见性:当一个线程修改了共享变量的值,其他线程能够看到这个修改
- 原子性:对共享变量的操作要么完全成功,要么完全失败,不会中断。对于一些基本的操作(如
i++)来说,JMM 并不保证其原子性,需要通过同步手段来确保 - 有序性:JMM 保证每个线程内的代码执行顺序,但不一定保证所有线程之间的执行顺序。为了确保线程之间操作的顺序,JMM 提供了同步机制来控制
JMM 中的关键规则
线程间的可见性保证:
- 可见性问题的核心是,当一个线程修改了共享变量,其他线程如何及时看到这个修改。JMM 的设计通过内存同步(比如锁机制、
volatile关键字、synchronized关键字等)来确保可见性 volatile关键字:声明为volatile的变量会直接从主内存中读取,而不是从线程的工作内存中读取。写入volatile变量时,JMM 会保证该写操作对其他线程可见。volatile确保了可见性,但不能保证原子性和有序性
原子性保障:
- 在 JMM 中,只有一些基本的操作(如读取和写入一个
long或double类型的变量)是原子的。对于复合操作(如i++),如果不加同步,可能会出现原子性问题 - 原子性问题的解决方法:使用
synchronized、ReentrantLock等同步机制来确保原子性
有序性保障:
- JMM 规定了每个线程内的指令执行顺序,但在不同线程之间,JMM 不保证执行顺序。为了控制执行顺序,可以使用
synchronized、volatile或Lock等手段 synchronized关键字:synchronized用于确保代码块的互斥执行,并在释放锁时会刷新工作内存中的值到主内存,从而保证线程间的可见性和顺序性volatile关键字:保证了变量的写操作立即刷新到主内存,且对该变量的读操作总是直接从主内存读取,避免了线程之间的数据不一致性
JMM 的实现和底层原理
- MESI 协议(Modified, Exclusive, Shared, Invalid),用于多核 CPU 之间缓存数据的一致性
- 内存屏障(Memory Barrier) :用于禁止指令重排,确保特定操作的顺序执行
volatile语义
可见性(Visibility)
- 保证:当一个线程修改了
volatile变量的值,新值会立即被刷新到主内存;其他线程在读取该变量时,会从主内存中重新加载最新值 - 实现机制:通过插入
Memory Barrier(内存屏障)或缓存一致性协议(如 MESI 协议)强制同步主内存和工作内存的数据
原子性(Atomicity)
- 单变量操作:对
volatile变量的读写操作是原子性的(例如count++不会被拆分为read+increment+write) - 复合操作:
volatile不能保证复合操作的原子性(例如i++或a = b + c),仍需借助synchronized或AtomicInteger等类
禁止指令重排序(Ordering)
- 编译器优化:编译器和处理器可能会对指令进行重排序以提高性能
- 读操作:在读取
volatile变量前插入Load Barrier,禁止之前的读/写操作被重排到其后 - 写操作:在写入
volatile变量后插入Store Barrier,禁止之后的读/写操作被重排到其前 - 效果:保证
volatile变量的读写顺序符合程序逻辑
happens-before规则
程序顺序规则
- 同一线程内,代码执行顺序与书写顺序一致(编译器和处理器可能重排指令,但需保证单线程结果不变)
监视器锁规则
- Lock → Unlock:对同一锁的
synchronized块,Lock操作必在Unlock前发生 - Unlock → Lock:
Unlock后,其他线程的Lock操作才能获取该锁
volatile 变量规则
- 写 → 读:对
volatile变量的写操作,必在后续读操作之前完成 - 读 → 写:对
volatile变量的读操作,必在后续写操作之前完成
线程启动规则
Thread.start()必须在新建线程的任何操作之前发生
线程终止规则
- 线程的
run()方法结束(正常或异常退出)必在join()返回之前发生
中断规则
- 对线程的
interrupt()调用必在该线程检测到中断状态(如isInterrupted())之前发生
对象终结规则
- 对象的
finalize()方法执行完必在字段被垃圾回收之前发生(注:finalize()已废弃)
传递性规则
- 若 A → B 且 B → C,则 A → C(可通过多条规则推导复杂顺序约束)
深入理解Java虚拟机-JAVA内存模型与线程的更多相关文章
- java虚拟机10.内存模型与线程
多任务处理在现代计算机操作系统中是一项必备的功能,让计算机同时去做几件事情,不仅是因为计算机的运算能力强大了,更重要的原因是计算机的运算速度与它的存储和通信子系统速度的差距太大,大量的时间都花费在磁盘 ...
- Java虚拟机:内存模型详解
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 我们都知道,当虚拟机执行Java代码的时候,首先要把字节码文件加载到内存,那么这些类的信息都存放在内存中的哪个区域呢?当我们创建一个对象实 ...
- java虚拟机的内存模型
一.为什么要了解java虚拟机的内存模型 java虚拟机作为java代码运行的平台,是java技术的基石.了解java虚拟机的内存模型也就变得十分必要.它能帮助我们更好的了解java代码的运行机制,更 ...
- Java虚拟机—Java8内存模型(整理版)
1.概述 对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要手动释放内存,不容易出现内存泄露和内存溢出问题.一旦出现内存泄露和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,排查错误 ...
- 深入理解JAVA虚拟机(内存模型+GC算法+JVM调优)
目录 1.Java虚拟机内存模型 1.1 程序计数器 1.2 Java虚拟机栈 局部变量 1.3 本地方法栈 1.4 Java堆 1.5 方法区(永久区.元空间) 附图 2.JVM内存分配参数 2.1 ...
- Java虚拟机之内存模型
一.java并发基础 在并发编程中存在两个关键问题①线程之间如何通信 ②线程之间如何同步. 通信 通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. ...
- JAVA虚拟机21---JAVA内存模型
1.Amdahl定律和摩尔定律 并发处理的广泛应用是Amdahl定律代替摩尔定律成为计算机性能发展源动力的根本原因,也是人类压榨计算机运算能力的最有力武器. Amdahl定律通过系统中并行化与串行化的 ...
- 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码
程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...
- java虚拟机的内存机制
我们都知道,java程序的跨平台性离不开java虚拟机,虚拟机隔绝了底层操作系统,使得java程序可以直接运行在虚拟机之上.所以,对java的学习,离不开对java虚拟机的学习与了解.下面简单整理下j ...
- 深入理解JVM(一)——JVM内存模型
JVM内存模型 Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: 1. 程序计数器 2. Java虚拟机栈 3. 本地方法栈 4. 堆 5. 方法区. ...
随机推荐
- Loadrunner11压测场景时最多跑5000个Vuser的问题解决办法
当我在用Loadrunner11压测场景时,设置Start Vusers是10000个.但实际运行是发现大约跑到5000个左右,后面的基本都是异常.如图: 查看Vusers运行详情: Error... ...
- Kotlin:【标准库函数】with、also、takeif、takeUnless
- 3. 使用sql查询csv/json文件内容,还能关联查询?
1. 简介 我们在前面的文章提到了calcite可以支持文件系统的数据源适配, 其实官方已经提供了相应的能力, 其支持csv和json的查询适配, 废话不多说, 直接展示. 2. Maven < ...
- InfluxDB UI 设置保留策略
InfluxDB UI 设置保留策略 创建Bucket时设置保留策略 在InfluxDB 2.x的UI中创建Bucket时,你可以直接设置其保留策略.以下是一个基本的步骤指南: 登录到InfluxDB ...
- Q:oracle中blog中截取部分字符串
blog报文中获取对应标签字符串 将xxx替换成需要查询的标签 to_char(substr(C_INPUT,instr(C_INPUT,'<xxx>')+length('<xxx& ...
- git pull报错:Pulling without specifying how to reconcile divergent branches is discouraged.
一.保存内容如下 二.翻译 三.设置为默认即可:git config pull.rebase false
- spring的事务管理有几种方式实现
一.事务的4个特性: 原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做,要么全部做. 一致性:数据不会因为事务的执行而遭到破坏. 隔离性:一个事务的执行,不受其他事务 ...
- mac环境下配置jdk
1.如果你是第一次配置环境变量,可以使用"touch .bash_profile" 创建一个.bash_profile的隐藏配置文件(如果你是为编辑已存在的配置文件,则使用&quo ...
- Windows 网络存储ISCSI
本文介绍网络存储ISCSI的主要知识点以及如何通过代码控制挂载. Windows网络存储有很多协议,我目前学习.稍微有了解的是FTP.SMB.ISCSI,FTP.SMB类似可以用来添加共享文件夹,或者 ...
- IDM 下载器 汉化注册激活
将以下程序更改为.bat文件.使用windows的管理员权限打开. 汉化于:https://github.com/WindowsAddict/IDM-Activation-Script @setloc ...