JVM-内存模型

内存模型

硬件的内存模型

在讲java内存模型之前,先来看看硬件的内存模型

在这里插入图片描述

CPU的处理速度和内存的读写不是一个数量级的,所以在CPU和主存之间加上了一层缓存

这种结构在单CPU的时候,处理的很好

但是当多CPU的时候,

在这里插入图片描述

这时候就会出现缓存一致性问题

当CPUA读取主存中的数据之后,对其进行修改,在将其刷新回主存的之前,CPUB读取主存中的数据,对其进行修改,将其刷新回主存,这时候CPUA也同时将其修改后的数据刷新回主存,那么这个数据到底以哪一个为准,这个就是缓存一致性问题

在这里插入图片描述

针对这个问题,就出现了缓存一致性协议

1、窥探性

2、目录型

有了这一层协议,在硬件层面,就解决了缓存一致性问题,即汇编语言能够运行在一个具有缓存一致性的内存视图中

Java的内存模型

设计编程语言的内存模型是为了能够该语言也可以拥有一个内存一致性的视图,于是在硬件内存模型之上,就有了高级语言的内存模型

Java内存模型就屏蔽了各种硬件的操作系统的内存差异,使得java可以正常的运行在各大操作系统上

在这里插入图片描述

虚拟机栈也可以叫做java方法栈,该栈中存放8大基础类型的数据和对象的引用

中存放着所有的java对象

内存读写指令

作用于主存作用于工作内存
lock:锁定load:加载数据
unlock:解锁store:存储数据
read:读取use:使用数据
write:写入assign:赋值

在这里插入图片描述

上面的图只是一种理想状态,会出现以下两种问题

1、可见性

当线程A将本地内存中的数据修改后,刷新回主存后,线程B直接使用本地内存中的数据,没有使用刷新后的数据,这就是可见性问题

2、原子性

当线程A修改了数据,还没刷新回主存,线程B也修改了数据,也要刷新回主存,那么这时候主存中应该刷新成哪个线程修改后的值呢

线程通信之间的同步问题,当多个线程在并发操作同一个数据的时候,会引发很多的问题,这些问题被总结为并发三要素

1、可见性

2、原子性

3、有序性

可见性

当一个线程修改了共享变量的值之后,其他所有使用该变量的线程都应该立刻得知此修改

两层含义

第一种含义

线程A修改了数据X,线程B需要使用到最新的数据X(这是线程B没有重新读取主存导致的)

public class Demo {
    static int a =1;
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            while (a == 1) {

            }
        });
        Thread thread2 = new Thread(() -> {
            a = 0;
        });
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        thread2.start();
    }
}

控制台不会返回响应码,而是一直死循环。

thread1开始循环的时候,本地内存中a=1,当thread2修改了a为0的时候,thread1并不知道,而是一直使用着a=1,所以会一直循环

两种解决方法

1、将a变量加一个修饰词volatile

如果一个共享变量被volatile修饰,那么该共享变量被修改后,将会直接写入主存,当其他线程读取该共享变量的时候,也会直接从主存中读取

2、使用synchronized包裹,并使用该数据

synchronized块中读写变量会隐式调用lockunlock指令

public class Demo {
    static int a =1;
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            while (a == 1) {
                synchronized(this){
                    int b = a + 1;
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            a = 0;
        });
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        thread2.start();
    }
}

第二层含义

线程B需要读取到线程A修改后的数据x,但是因为指令重排,在线程A未修改数据x之前,线程B读到了数据x

public class Demo2 {
    static int a =0;
    static boolean b =false;
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            a = 1;//1
            b = true;//2
        });
        Thread thread2 = new Thread(() -> {
            if (b) {//3
                int x = a;// 4  // 这个x一定是1吗?
                System.out.println(x); // 5
            }
        });
        thread1.start();
        thread2.start();
    }
}

在硬件内存模型的时候就说过,在底层会存在指令重排的情况,

我们觉得的顺序应该是

1->2->3->4

但是在编译后顺序有可能就变成了

2->3->4->1

这也是一种可见性的问题

同样的这里我们也可以使用上述两种方法来解决这种问题

volatile是禁止了当前变量与之前的代码语句进行指令重排

synchronized就是将两段代码分别捆绑在一起,那么无论在thread1中怎么指令重排,都不会影响thread2对于变量的读取

Happens-Before原则

我们平时很少遇到可见性问题,因为我们站在了前人的肩膀上,设计内存模型的前辈已经帮我们解决了此问题,这就是Happens-Before原则

定义:对于两个操作A和操作B,这两个操作可以在不同的线程中执行,如果A Happens-Before B(即A先于B执行),那么可以保证当A操作执行完后,A操作的执行结果对B操作是可见的

  1. 程序顺序原则
  2. 锁定原则
  3. volatile原则
  4. 线程启动原则
  5. 线程结束原则
  6. 中断规则
  7. 终结器规则
  8. 传递性原则

原子性

一个操作要么全部执行成功,要么全部执行失败

1、单指令原子操作

2、利用锁的组合指令原子操作

有序性

指令重排在单线程环境下不会出现什么问题,但是在多线程环境下,可能导致有的代码执行顺序修改后可能会导致与顺序执行的结果不同

这里可以使用Happens-Before原则来解决问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
JVM内存模型是指Java虚拟机在运行时对内存的组织和管理方式。它定义了JVM内存的不同区域以及各个区域的作用和特点。 JVM内存模型可以分为以下几个部分: 1. 程序计数器(Program Counter Register):每个线程都有自己的程序计数器,用于记录当前线程执行的字节码指令的地址。 2. Java虚拟机栈(Java Virtual Machine Stacks):每个线程在执行Java方法时会创建一个对应的栈帧,栈帧用于存储方法的局部变量、操作数栈、方法返回值等信息。 3. 堆(Heap):堆是JVM中最大的一块内存区域,被所有线程共享。它用于存储对象实例和数组。堆内存由垃圾回收器自动管理,负责对象的分配和释放。 4. 方法区(Method Area):方法区用于存储已加载类的信息、静态变量、常量、即时编译器编译后的代码等。在JDK 8及以后的版本中,方法区被元空间(Metaspace)所取代。 5. 运行时常量池(Runtime Constant Pool):每个类或接口在编译后都会生成一个运行时常量池,用于存放编译器生成的字面量和符号引用。 6. 本地方法栈(Native Method Stacks):本地方法栈用于执行本地方法(Native Method)的栈。 7. 直接内存(Direct Memory):直接内存不是JVM管理的堆内存,而是通过操作系统本地IO直接分配的内存。一般在使用NIO(New Input/Output)时会使用到直接内存。 这些内存区域共同组成了JVM内存模型,对于Java程序的运行和性能有着重要的影响。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

芝麻\n

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

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

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

打赏作者

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

抵扣说明:

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

余额充值