01-JVM详解

JVM详解

一、JVM简化模型

JVM模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aIeOUZsT-1627999967617)(./imgs/jvm-1.jpg)]

·线程共享:方法区、堆。
·线程私有:虚拟机栈、本地方法栈、程序计数器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lkaiFK84-1627999967619)(./imgs/image-20210609140033139.png)]

对象向JVM申请空间 流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvkBx9mw-1627999967621)(./imgs/image-20210609141615734.png)]

二、类加载器

2.1 类别加载到jvm开始待卸载出内存,整个生命周期如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-84aOV0Vx-1627999967623)(./imgs/image-20210609145321096.png)]

·加载:查找并加载类文件的二进制数据
·连接:就是将已读入内存的类的二进制数据合并到 JVM 运行时环境中去,包含以下步骤:
	·验证:确保被加载类的正确性
	·准备:为类的 静态变量 分配内存,并初始化
	·解析:把常量池中的符号引用转换成直接引用
·初始化:为类的静态变量赋初始值

2.2 类加载器类型

·Java 虚拟机自带的加载器包括以下几种:
	·启动类加载器(BootstrapClassLoader)
	·平台类加载器(PlatformClassLoader) JDK8:扩展类加载器(ExtensionClassLoader)
	·应用程序类加载器(AppClassLoader)
·用户自定义的加载器:是 java.lang.ClassLoader 的子类,用户可以定制类的加载方式;只不过自定义类加载器其加载的顺序是在所有系统类加载器的最后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G2GGpLCa-1627999967628)(./imgs/image-20210609150234071.png)]

2.3 双亲委派原则

·JVM中的 ClassLoader 通常采用双亲委派模型,要求除了启动类加载器外,其余的类加载器都应该有自己的父级加载器。这里的父子关系是组合  而不是继承,工作过程如下:
	1.一个类加载器接收到类加载请求后,首先搜索它的内建加载器定义的所有“具名模块”
	2.如果找到了合适的模块定义,将会使用该加载器来加载
	3.如果 class 没有在这些加载器定义的具名模块中找到,那么将委托给父级加载器,直到启动类加载器
	4.如果父级加载器反馈它不能完成加载请求,比如在它的搜索路径下找不到这个类,那子类加载器才自己来加载
	5.在类路径下找到的类将成为这些加载器的无名模块
·双亲委派模型对于保证 Java 程序的稳定运作很重要,可以避免一个类被加载多次
·实现双亲委派的代码在 java.lang.ClassLoader 的 loadClass() 方法中,如果自定义类加载器的话,推荐覆盖实现 findClass() 方法
·如果有一个类加载器能加载某个类,称为 定义类加载器,所有能成功返回该类的 Class 的类加载器 都被称为初始类加载器

2.4 双亲委派模型的说明

·双亲委派模型对于保证 Java 程序的稳定运作很重要
·实现双亲委派的代码 java.lang.ClassLoader 的 loadClass() 方法中,如果自定义类加载器的话,推荐覆盖实现 findClass() 方法
·如果有一个类加载器能加载某个类,称为 定义类加载器,所有能成功返回该类的 Class 的类加载器 都被称为 初始化加载器
·如果没有指定父加载器,默认就是启动类加载器
·每个类加载器都有自己的命名空间,命名空间由该类加载器及其所有父加载器所加载的类构成,不同的命名空间,可以出现类的全路径名 相同的情况
·运行时包由同一个类加载器的类构成,决定两个类是否属于同一个运行时包,不仅要看全路径名是否一样,还要看定义类加载器是否相同。只有属于同一个运行时包的类才能实现相互包内可见

三、类连接和初始化

3.1 类的初始化

·类的初始化就是为类的静态变量赋初始值,或者说是执行类构造器 方法的过程
	1.如果类还没有加载和连接,就先加载和连接
	2.如果类存在父类,且父类没有初始化,就先初始化父类
	3.如果类中存在初始化语句,就依次执行这些初始化语句
	4.如果是接口的话:
		1.初始化一个类的时候,并不会先初始化它实现的接口
		2.初始化一个接口的时候,并不会先初始化它的父接口
		3.只有当程序首次使用接口里面的变量或者是调用接口方法的时候,才导致接口初始化
	5.调用 Classloader 类的 loadClass 方法类装载一个类,并不会初始化这个类,不是对类的主动使用

四、JVM 内存分配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pmaTzGys-1627999967629)(D:/资料/日常记录/复习记录/imgs/image-20210609160539679.png)]

JVM的简化架构和运行时数据区

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-co3FvqxX-1627999967630)(D:/资料/日常记录/复习记录/imgs/image-20210609160629358.png)]

4.1 运行时数据区

·PC 寄存器、Java虚拟机栈、Java堆、方法区、运行时常量池、本地方法栈等
4.1.1 PC 寄存器
·每个线程都拥有一个PC寄存器,是线程私有的,用来存储指向下一条指令的地址
·在创建线程的时候,创建相应的PC寄存器
·执行本地方法时,PC寄存器的值为 undefined
·是一块比较小的内存空间,是唯一一个在JVM规范中没有规定 OutOfMemoryError 的内存区域
4.1.2 Java栈
·栈由一系列帧(栈帧)(Frame)组成(因此Java栈也叫做帧栈),是线程私有的
·栈帧用来保存一个方法的局部变量、操作数栈(Java没有寄存器,所有参数传递使用操作数栈)、常量池指针、动态链接、方法返回等
·每一次方法调用创建一个帧,并压栈,退出方法的时候,修改栈顶指针就可以把栈帧中的内容销毁
·局部变量表存放了编译期可知的各种基本数据类型和引用类型,每个 slot 存放32位的数据,long、double、占两个槽位
·栈的优点: 存取速度比堆块,仅次于寄存器
·栈的缺点:存在栈中的数据大小、生存区是在编译器决定的,缺乏灵活性
4.1.3 Java堆
·用来存放应用系统创建的对象和数组,所有线程共享 Java 堆
·GC主要管理堆空间,对分代GC来说,堆也是分代的
·堆的优点:运行期动态分配内存大小,自动进行垃圾回收;
·堆的缺点:效率相对较慢
4.1.4 方法区
·方法区是线程共享的,通常用来保存装载的类的结构信息
·通常和元空间关联在一起,但具体的跟JVM实现和版本有关
·JVM规范把方法区描述为堆的一个逻辑部分,但它有一个别名称为 Non-heap(非堆),应是为了与 Java 堆分开
4.1.5 运行时常量池
·是Class文件中每个类或接口的常量池表,在运行期间的表示形式,通常包括:类的版、字段、方法、接口等信息
·在方法区中分配
·通常在加载类和接口到JVM后,就创建相应的运行时常量池
4.1.6 本地方法栈
·在 JVM 中用来支持 native 方法执行的栈就是本地方法栈
栈、堆、方法区交互关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k6ody4IL-1627999967631)(D:/资料/日常记录/复习记录/imgs/image-20210609162722757.png)]

4.2 Java堆内存模型和分配

4.2.1 Java堆内存概述
·Java 堆用来存放应用系统创建的对象和数组,所有线程共享Java堆
·Java 堆是在运行期动态分配内存大小,自动进行垃圾回收
·Java 垃圾回收(GC)主要就是回收堆内存,对分代GC来说,堆也是分代的

Java堆的结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RzZhMAhD-1627999967633)(D:/资料/日常记录/复习记录/imgs/image-20210609163051509.png)]

·新生代用来存放新分配的对象;新生代中经过垃圾回收,没有回收掉的对象,被复制到老年代
·老年代存储对象比新生代存储对象的年龄大得多
·老年代存储一些大对象
·整个堆大小 = 新生代 + 老年代
·新生代 = Eden + 存活区(From To)
·从前的持久代,用来存放Class、Method 等元信息的区域,从 JDK8 开始去掉了,取而代之的是元空间(MetaSpace),元空间并不在虚拟机里面,而是直接使用本地内存
4.2.2 对象的访问定位
  • 使用句柄:Java堆中会划分出一块内存来作为句柄池,reference 中存储句柄的地址,句柄中存储对象的实例数据和类元数据的地址,如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LuRuP3ET-1627999967635)(D:/资料/日常记录/复习记录/imgs/image-20210609163740378.png)]

  • 使用指针:Java堆中会存放访问类元数据的地址,reference存储的就直接是对象的地址,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4J5kc3tv-1627999967636)(D:/资料/日常记录/复习记录/imgs/image-20210609163915161.png)]

4.2.3 Java堆的参数
-Xms:初始化堆大小,默认物理内存的 1/64
-Xmx:最大堆大小,默认物理内存的 1/4
-Xmn:新生代大小,默认整个堆的 3/8
-XX:+HeapDumpOnOutOfMemoryError:OOM时导出堆到文件
-XX:+HeapDumpPath:导出 OOM 的路径
-XX:NewRatio:老年代与新生代的比值,如果 xms=xmx,且设置了 xmn 的情况,该参数不用设置
-XX:SurvivorRatio:Eden区和Survivor区的大小比值,设置为8,则两个 Survivor 区与一个Eden区的比值为 2:8,一个 Survivor    	占整个新生的 1/10
-XX:OnOutOfMemoryError:在OOM时,执行一个脚本
-Xss:通常只有几百k,决定了函数调用的深度
4.2.4 元空间的参数
-XX:MetaspaceSize:初始空间大小
-XX:MaxMetaspaceSize:最大空间,默认是没有限制的
-XX:MinMetaspaceFreeRatio:在GC之后,最小的Metaspace剩余空间容量的百分比
-XX:MaxMetaspaceFreeRatio:在GC之后,最大的Metaspace剩余空间容量的百分比

4.3 栈帧

栈帧是用于支持JVM进行方法调用和方法执行的数据结构
栈帧随着方法调用而创建,随着方法结束而销毁
栈帧里面存储了方法的局部变量表、操作数栈、动态链接、方法返回地址等信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-35xV7dW5-1627999967637)(D:/资料/日常记录/复习记录/imgs/image-20210609165402225.png)]

4.3.1 局部变量表
·局部变量表:用来存放方法参数和方法内部定义的局部变量的存储空间
	1.以变量槽 slot 为单位,目前一个 slot 存放32位以内的数据类型
	2.对于64位的数据占2个slot
	3.对于实例方法,第0位 slot 存放的是 this,然后从1到n,依次分配给参数列表
	4.然后根据方法体内部定义的变量顺序和作用域来分配 slot
	5.slot 是复用的,以节省栈帧的空间,这种设计可能会影响系统的垃圾收集行为
4.3.2 局部变量表
操作数栈:用来存放方法运行期间,各个指令操作的数据
	操作数栈中元素的数据类型必须和字节码指令的顺序严格匹配
	虚拟机在实现栈帧的时候可能会做一些优化,让两个栈帧出现部分重叠区域,以存放公用的数据
4.3.3 动态链接
动态链接:每一个栈帧持有一个指向运行时常量池中该栈帧所属方法的引用,以支持方法调用过程的动态链接
	静态解析:类加载的时候,符号引用就转化为直接引用
	动态链接:运行期间转化为直接引用
4.3.4 方法返回地址
方法返回地址:方法执行后返回的地址

五、垃圾回收

5.1 垃圾回收基础

什么是垃圾:简单说就是内存中已经不再被使用到的内存空间就是垃圾

5.1.1 GC 类型
MinorGC / YoungGC:发生在新生代的收集动作
MajorGC / OldGC:发生在老年代的GC,目前只有CMS收集器会有单独收集老年代的行为
MixedGC:收集整个新生代以及部分老年代,目前只有G1收集器会有这种行为
FullGC:收集整个Java堆和方法区的GC
5.1.2 垃圾收集类型
串行收集:GC单线程内存回收、会暂停所有的用户线程,如:Serial
并行收集:多个GC线程并发工作,此时用户线程是暂停的,如:Parallel
并发收集:用户线程和GC线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,如:CMS
5.1.3 判断类无用的条件
JVM 中该类的所有实例都已经被回收
加载该类的 ClassLoader 已经被回收
没有任何地方引用该类的 Class 对象
无法在任何地方通过反射访问这个类

5.2 垃圾回收算法

5.2.1 标记清除算法
标记清除算法(Mark-Sweep):分为标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些对象
优点:简单
缺点:
	效率不高,标记和清除的效率都不高
	产生大量不连续的内存碎片,从而导致在分配大对象时触发GC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bp8w9C6D-1627999967639)(D:/资料/日常记录/复习记录/imgs/image-20210610150155924.png)]

5.2.2 复制算法
复制算法(Copying):把内存分成两块完全相同的区域,每次使用其中一块,当一块使用完了,就把这块上还存活的对象拷贝到另外一块,然后把这块清除掉
优点:实现简单,运行高效,不用考虑内存碎片问题
缺点:内存有些浪费
JVM实际实现中,是将内存分为一块较大的Eden区和两块较小的 Survivor 空间,每次使用Eden和一块 Survivor,回收时,把存活的对象复制到另一块 Survivor
HotSpot 默认的 Eden 和 Survivor 比是 8:1,也就是每次能用 90% 的新生代空间
如果 Survivor 空间不够,就要依赖老年代进行分配担保,把放不下的对象直接进入老年代

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahfd9S9V-1627999967640)(D:/资料/日常记录/复习记录/imgs/image-20210610150430765.png)]

5.2.3 标记整理算法
标记整理算法(Mark-Compact):由于复制算法在存活对象比较多的时候,效率较低,且有空间浪费,因此老年代一般不会选用复制算法,老年代多选用标记整理算法
标记过程跟标记清除算法一样,但后续不是直接清除可回收对象,而是让所有存活对象都向一端移动,然后直接清除边界以外的内存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxaajo8o-1627999967641)(D:/资料/日常记录/复习记录/imgs/image-20210610150548039.png)]

5.3 垃圾收集器

  • 串行收集器、并行收集器、新生代Parallel、Scavenge收集器、CMS、G1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AL7wH30g-1627999967643)(D:/资料/日常记录/复习记录/imgs/image-20210610163848840.png)]

5.3.1 串行收集器
Serial(串行)收集器 / Serial Old 收集器,是一个单线程的收集器,在垃圾收集时,会 Stop-the-World
优点:简单,对于单cpu,由于没有多线程的交互开销,可能更高效,是默认的 Client 模式下的新生代收集器
使用 -XX:+UseSerialGC 来开启,会使用:Serial + SerialOld 的收集器组合
新生代使用复制算法,老年代使用标记-整理算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mAWVlKq7-1627999967644)(D:/资料/日常记录/复习记录/imgs/image-20210610162426935.png)]

5.3.2 并行收集器

并行收集器包括:ParNew收集器、新生代Parallel Scavenge 收集器

5.3.2.1 ParNew收集器
ParNew(并行)收集器:使用多线程进行垃圾回收,在垃圾收集时,会Stop-the-World
在并发能力好的 CPU 环境里,它停顿的时间要比串行收集器短;但对于单 CPU 或并发能力较弱的CPU,由于多线程的交互开销,可能比串行回收器更差
是 Server 模式下首选的新生代收集器,且能和 CMS 收集器配合使用
不再使用 -XX:+UseParNewGC来单独开启
-XX:ParallelGCThreads:指定线程数,最好与 cpu 数量一致

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ooLv9KXi-1627999967645)(D:/资料/日常记录/复习记录/imgs/image-20210610163650440.png)]

5.3.2.1 新生代Parallel Scavenge 收集器
新生代 Parallel Scavenge 收集器 / Parallel Old 收集器:是一个应用于新生代的,使用复制算法的、并行的收集器.
与 ParNew 很类似,但更关注吞吐量,能最高效率的利用 CPU,适合运行后台应用.

使用 -XX:+UseParallelGC 来开启
使用 -XX:+UseParallelOldGC 来开启老年代使用 ParallelOld收集器,使用 Parallel Scavenge + Parallel Old 的收集器组合
-XX:MaxGCPauseMillis:设置GC 的最大停顿时间
新生代使用复制算法,老年代使用标记-整理算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDdMWLGh-1627999967646)(D:/资料/日常记录/复习记录/imgs/image-20210610164229839.png)]

5.3.3 CMS收集器
·CMS(Concurrent Mark and Sweep 并发标记清除)收集器分为:初始标记:只标记GC Roots 能直接关联到的对象;并发标记:进行GC ·Roots Tracing 的过程
·重新标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
·并发清除:并发回收垃圾对象
·在初始化标记和重新标记两个阶段还是会发生 Stop-the-World
·使用标记清除算法,多线程并发收集的垃圾收集器
·最后的重置线程,指的是清空跟收集相关的数据并重置,为下次收集做准备
·优点:低停顿,并发执行
·缺点:
	并发执行,对 CPU 资源压力大
	无法处理 在处理过程中 产生的垃圾(浮动垃圾),可能导致 FullGC
	采用的标记清除算法会导致大量碎片,从而在分配大对象可能触发 FullGC
·开启:-XX:UseConcMarkSweepGC:使用 ParNew + CMS + Serial Old 的收集器组合,Serial Old 将作为 CMS 出错的后备收集器
·-XX:CMSInitiatingOccupancyFraction:设置 CMS 收集器在老年代空间被使用多少后触发回收,默认 80%

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qjrJeFr-1627999967647)(D:/资料/日常记录/复习记录/imgs/image-20210610170102477.png)]

5.3.4 G1收集器
·G1(Garbage-First)收集器:是一款面向服务应用的收集器,与其他收集器相比,具有以下特点:
	1.G1 把内存划分成多个独立的区域(Region)
	2.G1 仍采用分代思想,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分Region的集合,且不需要 Region 是连续的
·G1 能充分利用多 CPU 、多核环境硬件优势,尽量缩短 STW
·G1 整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片
·G1 的停顿可预测,能明确指定在一个时间段内,消耗在垃圾收集上的时间不能超过多长时间
·G1 跟踪各个 Region 里面垃圾堆的价值大小,在后台维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限时间内的	  高效收集
·垃圾收集:
	·初始标记:只标记GC Roots 能直接关联到的对象
	·并发标记:进行 GC Roots Tracing 的过程
	·最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
·筛选回收:根据时间来进行价值最大化的回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M48HzXPI-1627999967649)(D:/资料/日常记录/复习记录/imgs/image-20210610170821798.png)]

使用和配置G1:-XX:+UseG1GC:开启G1,默认就是G1
-XX:MaxGCPauseMillis = n :最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间
-XX:InitiatingHeapOccupancyPercent = n:堆占用了多少的时候就触发GC,默认为45
-XX:NewRatio = n:默认为2
-XX:SurvivorRatio = n:默认为8
-XX:MaxTenuringThreshold = n:新生代到老年代岁数,默认是15
-XX:ParallelGCThreads = n:并行GC的线程数,默认值会根据平台不同而不同
-XX:ConcGCThreads = n:并发 GC 使用的线程数
-XX:G1ReservePercent = n:设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险,默认值是 10%
-XX:G1HeapRegionSize = n:设置的 G1 区域的大小。值是2的幂,范围是1MB到32MB,目标是根据最小的Java堆大小划分出约2048个区域

六、高效并发

6.1 Java内存模型和内存间的交互操作

6.1.1 Java内存模型
·内存模型:在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象
·Java 内存模型主要关注 JVM 中把变量值存储到内存和从内存中取出变量值这样的底层细节文出处链接及本声明。
·所有变量(共享的)都存储在主内存中,每个线程都有自己的工作内存;工作内存中保存该线程使用到的变量的主内存副本拷贝
·线程对变量的所有操作(读、写)都应该在工作内存中完成
·不同线程不能相互访问工作内存,交互数据要通过主内存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GyDztRE4-1627999967651)(D:/资料/日常记录/复习记录/imgs/image-20210610172242341.png)]

6.1.2 内存间的交互操作
Java内存模型规定了一些操作来实现内存间交互,JVM会保存它们是原子的
·lock:锁定,把变量标识为线程独占,作用于主内存变量
·unlock:解锁,把锁定的变量释放,别的线程才能使用,作用于主内存变量
·read:读取,把变量从主内存读取到工作内存
·load:载入,把read读取到的值放入工作内存的变量副本中
·use:使用,把工作内存中一个变量的值传递给执行引擎
·assign:赋值,把从执行引擎接收到的值赋给工作内存里面的变量
·store:存储,把工作内存中一个变量的值传递到主内存中
·wirte:写入,把 store 进来的数据存放如主内存的变量中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oI9i6NRk-1627999967652)(D:/资料/日常记录/复习记录/imgs/image-20210610172644555.png)]

6.1.3 内存间的交互操作的规则
·不允许 read 和 load 、store 和 write 操作之一单独出现,以上两个操作必须按照顺序执行,但不保证连续执行,也就是说,read 和 load 之间、store 与 write 之间是可插入其他指令的
·不允许一个线程丢弃它的最近的 assign 操作,即变量在工作内存中改变了之后必须把该变化同步回主内存
·不允许一个线程无原因地(没有发生过任何 assign 操作)把数据从线程的工作内存同步回主内存中
·一个新的变量只能从主内存中 ”诞生“,不允许在工作内存中直接使用一个未被初始化的变量,也就是对一个变量实施 use 和 store 操作之	  前,必须先执行过了 assign 和 load 操作
·一个变量在同一个时刻只允许一条线程对其执行 lock 操作,但 lock 操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的 unlock 操作,变量才会被解锁
·如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行 load 或 assign 操作初始化变量的值
·如果一个变量没有被 lock 操作锁定,则不允许对它执行 unlock 操作,也不能 unlock 一个被其他线程锁定的变量
·对一个变量执行 unlock 操作之前,必须先把此变量同步回主内存(执行 store 和 write 操作)

6.2 volatile特性

·多线程中的可见性
	·可见性:就是一个线程修改了变量,其他线程可以知道
	·保证可见性的常见方法:volatile、synchronized、final(一旦初始化完成,其他线程就可见)
·volatile
	·volatile 基本上是 JVM 提供的最轻量级的同步机制,用 volatile 修饰的变量,对所有的线程可见,即对 volatile 变量所做的写·	 操作能立即反映到其他线程中
		·用 volatile 修饰的变量,在多线程环境下仍然是不安全的
		·volatile 修饰的变量,是禁止指令重排优化的
		·适合使用 valatile 的场景
		·运算结果不依赖变量的当前值
		·确保只有一个线程修改变量的值

6.3 指令重排

指令重排:指的是 JVM 为了优化,在条件允许的情况下,对指令进行一定的重新排列,直接运行当前能够立即执行的后序指令,避开获取下一条指令所需数据造成的等待
线程内串行语义,不考虑多线程间的语义
不是所有的指令都能重排,比如:
	写后读 a = 1; b = a;写一个变量之后,再读这个位置
	写后写 a = 1;a = 2;写一个变量之后,再写这个变量
	读后写 a = b;b = 1;读一个变量之后,再写这个变量
以上语句不可重排,但是 a = 1;b = 2;是可以重排的
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile 变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A 先于 B,B 先于 C,那么 A 必然先于 C
线程的 start 方法先于它的每一个动作
线程的所有操作先于线程的终结
线程中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于 finalize() 方法

`

6.3 指令重排

指令重排:指的是 JVM 为了优化,在条件允许的情况下,对指令进行一定的重新排列,直接运行当前能够立即执行的后序指令,避开获取下一条指令所需数据造成的等待
线程内串行语义,不考虑多线程间的语义
不是所有的指令都能重排,比如:
	写后读 a = 1; b = a;写一个变量之后,再读这个位置
	写后写 a = 1;a = 2;写一个变量之后,再写这个变量
	读后写 a = b;b = 1;读一个变量之后,再写这个变量
以上语句不可重排,但是 a = 1;b = 2;是可以重排的
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile 变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A 先于 B,B 先于 C,那么 A 必然先于 C
线程的 start 方法先于它的每一个动作
线程的所有操作先于线程的终结
线程中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于 finalize() 方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值