Java 内存模型


1.Java内存模型的“底层原理”

  • 从Java代码到CPU指令的变化过程?

    • 最开始,我们编写的Java代码,即*.Java文件
    • 在编译Javac命令后,从刚才的*.Java文件会变出一个新的Java字节码文件,即*.class文件
    • JVM会执行刚才生成的*.class字节码文件,并把字节码文件转化为机器指令
    • 机器指令可以直接在CPU上执运行,也就是最终的程序执行
  • 而不同的JVM实现会带来不同的“翻译”,不同的CPU平台的机器指令干差万别,所以我们在Java代码层写的各种Lock,其实最后依赖的是JVM的具体实现(不同版本会有不同实现)和CPU的指令,才能帮我们达到线程安全的效果。

  • Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果

2.JVM内存结构 VS Java内存模型 VS Java对象模型

  • JVM内存结构,和Java虚拟机的运行时数据区域有关。
  • Java内存模型,和Java并发编程有关。
  • Java对象模型,和Java对象在虚拟机中的表现形式有关。

JVM内存结构:详情点这里 >>> Java运行时数据区域
Java虚拟机运行时数据区图
Java对象模型:详情点这里 >>> Java对象的创建、内存布局和访问定位
Java对象模型

3.JMM是什么?

  • 全称:Java Memory Model
  • 为什么需要JMM?
    • C语言不存在内存模型的概念
    • 很多并发操作(很多Native方法)依赖处理器,不同处理器结果不一样
    • 无法保证并发安全
    • 需要一个标准,让多线程运行的结果可预期
  • 是一组规范,需要各个JVM的实现来遵守JMM规范,以便于开发者可以利用这些规范,更方便地开发多线程程序。
  • 如果没有这样的一个JMM内存模型来规范,那么很可能经过了不同规则的重排序之后,导致不同的虚拟机上运行的结果不一样,这就造成了很大的问题。
  • volatile、synchronized、Lock等的原理都是JMM
  • 如果没有JMM,那就需要我们自己指定什么时候用内存栅栏等,那是相当麻烦的,幸好有了JMM,让我们只需要用同步工具和关键字就可以开发并发程序。
  • JMM最重要的三点内容:重排序、可见性、原子性

4.重排序

  • 什么是重排序:在线程1内部的两行代码的实际执行顺序和代码在Java文件中的顺序不一致,代码指令并不是严格按照代码语句顺序执行的,他们的顺序被改变了,这就是重排序。
  • 重排序的好处:提高处理速度
  • 重排序的3种情况
    • 编译器优化:包括JVM,JIT编译器等
    • CPU指令重排:就算编译器不发生重排,CPU也可能对指令进行重排序。
    • 内存的“重排序”:线程A的修改线程B却看不到,引出可见性问题。

5.可见性

  • 什么是可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改
  • 为什么会有可见性问题CPU有多级缓存,导致读的数据过期
    • 高速缓存的容量比主内存小,但是速度仅次于寄存器,所以在CPU和主内存之间就多了Cache层。
    • 线程间的对于共享变量的可见性问题不是直接由多核引起的,而是由多缓存引起的。
    • 如果所有的核心都只用一个缓存,那么也就不存在内存可见性问题。
    • 每个核心都会将自己需要的数据读到独占缓存中,数据修改后也是写入到缓存中,然后等待刷入到主存中。所以会导致有些核心读取的值是一个过期的值。
    • 所有的共享变量存在于主内存中,每个线程有自己的本地内存,而且线程读写共享数据也是通过本地内存交换的,所以才导致了可见性问题。
  • happens-before原则:先行发生原则,动作A发生在动作B之前,B保证能看见A,这就是happens-before。
    • 程序次序规则:同一个线程内,按照代码出现的顺序,前面的代码先行于后面的代码,准确的说是控制流顺序,因为要考虑到分支和循环结构。
    • 管程锁定规则:一个unlock操作先行发生于后面(时间上)对同一个锁的lock操作。
    • volatile变量规则:对一个volatile变量的写操作先行发生于后面(时间上)对这个变量的读操作。
    • 线程启动规则:Thread的start()方法先行发生于这个线程的每一个操作。
    • 线程终止规则:线程的所有操作都先行于此线程的终止检测。可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测线程的终止。
    • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupt()方法检测线程是否中断
    • 对象终结规则:一个对象的初始化完成先行于发生它的finalize()方法的开始。
    • 传递性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C。
  • volatile关键字:详情点这里 >>> volatile关键字
  • synchronized关键字:详情点这里 >>> synchronized关键字

6.原子性

  • 什么是原子性:一系列操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分割的。
  • Java中的原子操作有哪些?
    • 除了long和double之外的基本类型(int、byte、boolean、short、char、float
    • 所有引用reference的复制操作,不管是32位的机器还是64位的机器
    • java.concurrent.Atomic.*包中所有类的原子操作。
  • long和double的原子性
    • 问题描述:官方文档、对于64位的值的写入,可以分为两个32位的操作进行写入、读取错误、使用volatile解决。
    • 结论:在32位上的JVM上,long和double的操作不是原子的,但是在64位的JVM上是原子的。
    • 实际开发中:商用Java虚拟机中不会出现,默认保证了原子性。

7.面试问题

1.JMM应用实例:单例模式的几种写法、单例和并发的关系?

详情点这里 >>> 单例模式的9种写法

2.讲一讲什么是Java内存模型?

本篇已提到。

3.volatile和synchronized异同?

本篇已提到。

4.什么是内存原子操作?Java中有哪些原子操作?生成对象的过程是不是原子操作?

本篇已提到。

5.什么是内存可见性?

本篇已提到。

6.64位的double和long写入的时候是原子的吗?

本篇已提到。

笔记来源:慕课网悟空老师视频《Java并发核心知识体系精讲》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

✦昨夜星辰✦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值