cpu缓存与内存
计算机工作时,由于cpu的计算任务速度远远大于从内存读取数据的速度,所以cpu引入了缓存,存取要使用的资源。
1 为什么cpu缓存可以解决速度不匹配问题
缓存的存在依据以下原理:
1 时间局部性:如果某个数据被访问,那么在不久的将来,它将再次被访问
2 空间局部性:如果某个数据被访问,那么与它相邻的数据块也将被访问
这样当cpu 在计算时计算资源可能已经存储在缓存中,cpu就不需要到内存中去加载,cpu可以直接进行计算。
如果cpu是单核,那么整个cpu只有一套L3、L2、L1缓存(现在计算机大部分都是3级缓存);如果cpu是多核,则每个核心都含有一套L1(甚至和L2)缓存,而共享L3(或者和L2)缓存。所以会出现cpu缓存中数据不一致的问题。基于此CPU引入了缓存一致性模型(MESI)。
M : modify 这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中,所以处在该状态下的数据要时间监听是否有其他缓存在内存中读取该行,如果有必须延迟到本地cache将这行数据刷新到主存中,并且将状态改为S(共享)才能执行。
E : Exclusive 这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中,缓存行也必须监听其他缓存读取内存中该缓存行的数据,如果有则该缓存行需要修改状态为S
S : Shared 这行数据有效,数据和内存中的数据一致,数据存在于cache中。缓存行业必须监听其他缓存使该缓存行无效或者独享该缓存行的请求。
I : 这行数据无效
java内存
java虚拟机为了屏蔽各种硬件和操作系统之间的差异,引入了java内存模型(简称JMM),用以实现java程序在各种平台下都能一致性的访问。. java内存模型是一种规范,它规范了java虚拟机和计算机内存是如何协同工作的,规定了一个线程如何和何时能看到其他线程修改过的共享变量,以及在必须时如何同步的访问共享变量。
Java的内存主要分为程序计数器、堆、栈、方法区。
- 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的指示器、属于线程私有的。java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。所以程序计数器用来标识此线程执行到哪个指令。
- 栈
栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型,java方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。主要存放局部变量、基本类型、对应的引用。
出错是抛出两种异常:
1 如果线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackoverflowEorror的异常
2 如果虚拟机栈可以可以动态扩展,扩展时没有合适的空间则抛出oom异常。
-
堆
堆是java虚拟机管理的最大一块内存。java的堆是所有线程共享的,此内存区域的唯一的唯一目的就是存放实例。java通过垃圾收集器来对此块内存进行管理。现在的收集器基本都采用分代收集算法,所以java堆可以细分为新生代和老年代,其中新生代又分为Eden空间,From Survivor空间、To Survivor空间。
-
方法区
java方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量即时编译器编译后的代码等数据。
所以每个线程在工作时,都会将存储在主存中的数据拷贝到工作内存中,此工作内存指的是cpu的缓存(或者寄存器)。这样当数据被加载到每个线程的缓存的时候,当多个线程同时读取了某一共享变量的时候,就会造成数据不一致的问题。java虚拟机为了解决该问题引入了java内存模型。
Java虚拟机定义了8种操作和几个规则来实现内存模型。其中8种操作包括 lock(锁定)、read(读取)、load(载入)、use(使用)、asign(赋值)、store(存储)、write(写入)、解锁。之后对这八种操作定义了使用它们的规则来保证数据的完整、一致。
- lock 作用于主内存将一个变量标记为一个线程独自占有
- read 作用于主内存,将变量值从主内存中读到工作内存中
- load 作用于工作内存,将变量值赋值到工作内存中对应的变量上
- use 作用于工作内存,把工作内存中的一个变量值传递给执行引擎
- assign 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
- store 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
- write 作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
如果要把一个变量从主内存中复制到工作内存中,就需要按顺序地执行read和load操作,如果把变量从工作内存中同步到主内存中,就需要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
同步规则
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步会主内存中
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或者assign)的变量。即就是对一个变量实施use和store操作之前,必须先自行assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现。
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量之前需要重新执行load或assign操作初始化变量的值。
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作