前言
今天的知识点有点涉及自己自习的不扎实的地方了(内存这东西真是学了就bu忘shi),所以今天在博客内总结一下。
首先不得不提,JVM内存模型和Java内存模型(JMM)是两个东西,JVM内存模型是与JVM的内部存储结构相关,而Java内存模型是与多线程编程相关。
java内存模型(JMM)
附上B站教学视频
这个老师讲的超级清楚
我们来看这个图(图壹),我们根据这个图来讲
这两个线程,共用一个变量intFlag
由于这个变量是公用的,是不是意味着一个线程改变它以后,另外一个线程就能感知到并作出相应改变呢?
这就要我们从JMM模型来分析了
首先我们要了解一下计算机的大致内存模型
计算机大致内存模型
左侧是最原始的内存模型,由于CPU的运算速度符合摩尔公式,程指数倍递增,而RAM主存速度是不会变的,这就导致了两者不匹配,RAM跟不上CPU,所以科学家就设计出了如右图的模型,即加入了CPU缓存(其实在缓存和RAM中间还有总线),使得两者匹配
好,在我们看过计算机大致的存储模型以后,我们再来讲一下Java虚拟机,也就是JVM。众所周知Java是一门跨平台的语言,即一处编译处处执行,依靠的就是Java虚拟机,Java虚拟机的实现,有一部分是模拟操作系统来实现的,现阶段我们可以近似看成如图壹的那样,也拥有一个主存一个总线,一个CPU(核),核内有工作内存(CPU缓存)和线程内容。
其中主存和CPU之间有这些操作
结合图壹,我们可以看出,一个线程在执行时,会先把这个量read进去,在自己的工作内存中复制一份,之后再use这个复制品,因此,一般的变量,在一个线程的修改,是不会影响另一个线程的,因为两者实际use的不是一个东西,他们use的都只是自己的工作内存中的复制品。
有序性、原子性、可见性
有序性:在线程内部的两行代码的实际执行顺序和代码在Java文件中的逻辑顺序不一致,代码指令并不是严格按照代码语句顺序执行的
原子性:指一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分的
可见性:指是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值
那么我们要如何保证这三个性质呢?
这宫里给出两个关键词
volatile
这个关键词保证了可见性,但是不保证原子性,什么意思呢
虽然由于多核CPU普及,最原始的lock、unlock被淘汰了,取而代之的是MESI缓存一致性协议,但是我们理解的时候还是可以用上lock和unlock,比如我们两个线程,一个在i++一个在i–,最终执行结束之后的i是什么呢?结果有三种,0,-1,1。因为两个线程执行的时候,是不能保证谁先执行完的,而根据MESI,先执行完的线程,write了主内存的i,并且在它write时,对i进行了lock,然后,后执行完的线程的嗅探机制检测到主线内的lock并使得该线程清空了它的工作内存里的i,使得自加或者自减操作最终只生效了一次,当然也可能第一个执行完,并且write后第二个线程才第一次将共享变量read并进行后续操作,这样就会i=0,即返回逻辑上的结果。
synchronized
synchronized语义表示锁在同一时刻只能由一个线程进行获取,当锁被占用后,其他线程只能等待。这样就保证了原子性,还是那个例子,同样的底层,只不过保证了只会执行i=0,的那种具体实现。
以上两者都能保证有序性,但有序性是相对线程内部的,即另外的线程看此线程,就可能是无序的。
JVM相关知识点与对象生命周期
JVM是什么
JVM(java virtual machine)也就是java虚拟机,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能模拟来实现的。Java虚拟机有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。
JVM有方法区、java堆、java栈(虚拟机栈)、本地方法栈、PC程序计数器。
程序计数器:
程序计数器是一块很小的内存空间,用于记录下一条要运行的指令。每个线程都需要一个程序计数器,各个线程之中的计数器相互独立,是线程中私有的内存空间。
** java虚拟机栈:**
Java虚拟机栈也是线程私有的内存空间,它和java线程同一时间创建,包括当前方法的所有本地变量信息栈中存放方法中的局部变量,方法的运行一定要在栈中,要先把方法给压入栈。等到超出作用域之后就消失(用完就丢)
本地方法栈:
本地方法栈和java虚拟机栈的功能相似,java虚拟机栈用于管理Java函数的调用,而本地方法栈用于管理本地方法的调用,但不是由Java实现的,而是由C实现的
java堆:
为所有创建的对象和数组分配内存空间,被JVM中所有的线程共享,也就是凡是new出来的东西都要放在堆当中,堆内存当中的所有东西都会有地址值,通过引用可以获得其地址值
方法区:
存放.class的相关信息,也就是编译后的文件存放在此,这里有方法的具体实现
那么什么时候会创建新的对象,即在堆里面写入数据,而不是直接在容量池里创建呢?
Character 类:缓存 0~127 的字符,其它需要新创建。
Byte,Short,Integer,Long 类:缓存 -128~127 的对象,其它值需要重新创建。
Float,Double 类:没有缓存,直接创建。
Boolean 类:缓存 true 和 false 两个对象,直接赋值即可。