《深刻理解 Java 虚拟机》笔记整理

正文

1、Java 内存区域与内存溢出异常

一、运行时数据区域

  • 程序计数器:当前线程所执行的字节码的行号指示器。线程私有。
  • Java 虚拟机栈:Java 方法执行的内存模型。线程私有。
  • 本地方法栈:Native 方法执行的内存模型。线程私有。
  • Java 堆:存放对象实例。分为新生代(Eden 空间、From Survivor 空间、To Survivor 空间)和老年代。线程共享。
  • 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。也称为“永久代”。线程共享。
  • 运行时常量池:方法区的一部分,用于存放编译期生成的各类字面量和符号引用。 线程共享。
  • 直接内存

二、对象的建立

类加载检查 -> 分配内存 -> 初始化零值 -> 设置对象头 -> 执行 init 方法。html

  • 类加载检查:检查 new 指令的参数可否在常量池中定位到一个类的符号引用,以及这个符号引用表明的类是否已被加载、解析和初始化过。
  • 分配内存:把一块肯定大小的内存从 Java 堆中划分出来。
  • 初始化零值:将分配到的内存空间初始化为零值(不包括对象头)。
  • 设置对象头:虚拟机须要对对象进行必要的设置,这些信息存放在对象的对象头中。
  • 执行 init 方法:把对象按照程序员的意愿进行初始化。

三、对象的内存布局

  • 对象头
    • Mark Word:存储对象自身的运行时数据。
    • 类型指针:存储对象的类元数据的指针。
  • 实例数据:对象真正存储的有效信息,也是在程序代码中所定义的各类类型的字段内容。
  • 对齐填充:仅仅起着占位符的做用。

四、对象的访问定位

  • 句柄:引用中存储的是对象的句柄地址。Java 堆中划分出一块内存做为句柄池,句柄中包含了对象实例数据、类型数据二者的具体地址信息。
  • 直接指针:引用中存储的直接就是对象的地址。

五、OutOfMemoryError 异常

  • Java 堆溢出
  • 虚拟机栈和本地方法栈溢出
  • 方法区和运行时常量池溢出
  • 本机直接内存溢出

2、垃圾收集器与内存分配策略

一、判断对象是否可用

  • 引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加 1;当引用失效时,计数器值减 1;任什么时候刻计数器为 0 的对象就是不可能再被使用的。
  • 可达性分析算法:经过一系列被称为“GC Roots”的对象做为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连时,则此对象不可用。

二、四种引用

  • 强引用:相似“Object obj = new Object()”的引用。只要强引用还存在,对象就永远不会回收。
  • 软引用:用来描述一些还有用但并不是必需的对象。内存不足时,对象有可能被回收。
  • 弱引用:用来描述非必需的对象,但强度比软引用弱。GC时,不管内存是否足够,对象都会被回收。
  • 虚引用:也称幽灵引用或幻影引用,虚引用不会对对象的生存时间构成影响。虚引用的惟一做用就是能在对象被回收时收到一个系统通知。

三、垃圾收集算法

  • 标记-清除算法:分为“标记”和“清除”两个阶段。首先标记出全部须要回收的对象,而后再统一回收全部被标记的对象。会产生大量不连续的内存碎片。
  • 复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当一块内存用完时,就将还存活的对象复制到另外一块,而后再把已使用过的内存空间一次清理掉。
  • 标记-整理算法:首先标记出全部须要回收的对象,而后将全部存活对象向一端移动,最后直接清理掉端边界之外的内存。
  • 分代收集算法:根据对象存活周期的不一样,将 Java 堆划分为新生代和老年代,而后根据各个年代的特色采用最适当的收集算法。
    • 新生代:采用复制算法。
    • 老年代:采用“标记-清除”或“标记-整理”算法。

四、垃圾收集器

  • Serial 收集器:单线程。新生代收集器。
  • ParNew 收集器:Serial 收集器的多线程版本。新生代收集器。
  • Parallel Scavenge 收集器:多线程。新生代收集器。关注吞吐量。
  • Serial Old 收集器:Serial 收集器的老年代版本。单线程。使用“标记-整理”算法。
  • Parallel Old 收集器:Parallel Scavenge 收集器的老年代版本。多线程。使用“标记-整理”算法。
  • CMS 收集器:并发收集器。使用“标记-清除”算法。关注点是如何缩短垃圾收集时用户线程的停顿时间。
  • G1 收集器:面向服务端应用。并行与并发、分代收集、空间整合、可预测停顿时间。

五、内存分配与回收策略

  • 对象优先在 Eden 分配。
  • 大对象直接进入老年代。
  • 长期存活的对象进入老年代。
  • 动态对象年龄断定。
  • 空间分配担保。

3、虚拟机性能监控与故障处理工具

一、JDK 的命令行工具

  • jps:显示正在运行的虚拟机进程。经常使用命令:jps -l
  • jstat:监视虚拟机各类运行状态信息。经常使用命令:jstat -gcutil <pid>
  • jinfo:显示虚拟机配置信息。经常使用命令:jinfo -flags <pid>
  • jmap:主要用于生成堆转储快照。经常使用命令:jmap -dump:format=b,file=<filename> <pid>
  • jhat:分析 jmap 生成的堆转储快照。经常使用命令:jhat <filename>
  • jstack:显示虚拟机当前时刻的线程堆栈信息。经常使用命令:jstack -l <pid>

二、JDK 的可视化工具

  • JConsole:Java 监视与管理控制台
  • VisualVM:多合一故障处理工具

4、类文件结构

一、无关性的基石

  • 各类不一样平台的虚拟机
  • 全部平台都统一使用的字节码存储格式

二、Class 类文件的结构

(1)Class 文件的数据类型

  • 无符号数:基本数据类型,以 u一、u二、u四、u8 来分别表明 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数。用于描述数字、索引引用、数量值或按照 UTF-8 编码构成字符串值。
  • :由多个无符号数或其余表做为数据项构成的复合数据类型,全部表都习惯性地以“_info”结尾。用于描述有层次关系的复合结构数据,整个 Class 文件本质上就是一张表。

(2)Class 文件格式

类型 名称 数量
u4 magic(魔数) 1
u2 minor_version(次版本号) 1
u2 major_version(主版本号) 1
u2 constant_pool_count(常量池容量计数器) 1
cp_info constant_pool(常量池) constant_pool_count - 1
u2 access_flags(访问标志) 1
u2 this_class(类索引) 1
u2 super_class(父类索引) 1
u2 interfaces_count(接口计数器) 1
u2 interfaces(接口索引集合) interfaces_count
u2 fields_count(字段表计数器) 1
field_info fields(字段表集合) fields_count
u2 methods_count(方法表计数器) 1
method_info methods(方法表集合) methods_count
u2 attributes_count(属性表计数器) 1
attribute_info attributes(属性表集合) attributes_count
  • 魔数:Class 文件的头 4 个字节,用于肯定该文件是否为 Class 文件。其值为:0xCAFEBABE(咖啡宝贝?)。
  • Class 文件的版本:第 五、6 个字节是次版本号,第 七、8 个字节是主版本号。
  • 常量池:能够理解为 Class 文件中的资源仓库。主要存放字面量和符号引用。每一项常量都是一个表。
  • 访问标志:用于识别一些类或接口层次的访问信息,包括:这个 Class 是类仍是接口、是否认义为 public、是否认义为 abstract、是否声明为 final(只有类可设置)等。
  • 类索引、父类索引与接口索引集合:Class 文件由这三项数据肯定这个类的继承关系。
  • 字段表集合:用于描述接口或类中声明的变量。包括类变量和实例变量,但不包括在方法内部声明的局部变量。
  • 方法表集合:用于描述接口或类中声明的方法。
  • 属性表集合:在 Class 文件、字段表、方法表均可以携带本身的属性表集合,以用于描述某些场景专有的信息。

三、字节码指令简介

  • 加载和存储指令:用于将数据在栈帧中的局部变量表和操做数栈之间来回传输。
  • 运算指令:用于对两个操做数以上的值进行某种特定运算,并把结果从新存入到操做数栈顶。
  • 类型转换指令:将两种不一样的数值类型进行相互转换。
  • 对象建立与访问指令。
  • 操做数栈管理指令:用于直接操做操做数栈。
  • 控制转移指令:让 Java 虚拟机有条件或无条件地从指定位置的指令继续执行程序,而不是从控制转移指令的下一条指令继续执行程序。可认为控制转移指令就是在有条件或无条件地修改 PC 寄存器的值。
  • 方法调用和返回指令。
  • 异常处理指令。
  • 同步指令:支持方法级的同步和方法内部一段指令序列的同步。

5、虚拟机类加载机制

一、类加载的过程

加载 -> 链接(验证、准备、解析) -> 初始化。java

  • 加载:获取二进制字节流,并在内存中生成一个表明这个类的 java.lang.Class 对象,做为方法区这个类的各类数据的访问入口。
  • 验证:确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。
    • 文件格式验证:验证字节流是否符合 Class 文件格式的规范,而且能被当前版本的虚拟机处理。
    • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
    • 字节码验证:经过数据流和控制流分析,肯定程序语义是合法的、符合逻辑的。
    • 符号引用验证:对符合引用进行匹配性校验,确保解析动做能正常执行。
  • 准备:为类变量分配内存并设置初始值。
  • 解析:将常量池内的符号引用替换为直接引用。
  • 初始化:根据程序员的主观计划去初始化类变量和其余资源。

二、类加载器

  • 启动类加载器(Bootstrap ClassLoader):负责将存放在 <JAVA_HOME>\lib 目录的,或者 -Xbootclasspath 参数所指定路径中的,能被虚拟机识别的类库加载到虚拟机内存中。
  • 扩展类加载器(Extension ClassLoader):负责加载 <JAVA_HOME>\lib\ext 目录中的,或者 java.ext.dirs 系统变量所指定路径中的全部类库。
  • 应用程序类加载器(Application ClassLoader):负责加载用户类路径上所指定的类库。

三、双亲委派模型

若是一个类加载器收到类加载的请求,它会先把这个请求委派给父加载器去完成,而不会本身去尝试加载这个类。只有父加载器没法完成这个加载请求时,子加载器才会尝试本身去加载。程序员

6、虚拟机字节码执行引擎

一、运行时栈帧结构

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。栈帧存储了方法的局部变量表、操做数栈、动态链接、方法返回地址和一些额外的附加信息。每个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。算法

  • 局部变量表:是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
  • 操做数栈:也称为操做栈,它是一个后入先出的栈。操做数栈的每个元素能够是任意的 Java 数据类型。
  • 动态链接:每一个栈帧都包含一个指向运行时常量池中,该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程当中的动态链接。
  • 方法返回地址:方法退出后须要返回到方法被调用的位置,程序才能继续执行。
  • 附加信息:虚拟机规范容许具体的虚拟机实现增长一些规范里没有描述的信息到栈帧中,例如与调试相关的信息。

2、方法调用

方法调用并不等于方法执行,方法调用阶段惟一的任务就是肯定被调用方法的版本(即调用哪个方法)。此时,在 Class 文件里存储的只是符号引用,而不是直接引用,只有在类加载期间,甚至是运行期间才能肯定目标方法的直接引用。数组

  • 解析:在类加载的解析阶段,将方法的符号引用转化为直接引用,这类方法调用称为解析。这种解析能成立的前提是:方法在程序执行以前有一个可肯定的调用版本,而且这个方法的调用版本在运行期不可改变,即“编译期可知,运行期不可变”。
  • 分派
    • 静态分派:在编译期依赖静态类型(又称外观类型)来定位方法执行版本的分派动做,称为静态分派。静态分派的典型应用是方法重载。
    • 动态分派:在运行期根据实际类型肯定方法执行版本的分派过程,称为动态分派。动态分派的典型应用是方法重写。

7、早期(编译期)优化

一、Javac 编译过程

(1)解析与填充符号表

  • 词法分析:将源代码的字符流转变为标记(Token)集合,标记是编译过程的最小元素,关键字、变量名、字面量、运算符均可以成为标记。
  • 语法分析:根据 Token 序列构造抽象语法树。
  • 填充符号表:符号表是由一组符号地址和符号信息构成的表格,能够把它想象成哈希表中 K-V 值对的形式。

(2)注解处理

在编译期间对注解进行处理。能够读取、修改、添加抽象语法树中的任何元素。安全

(3)语义分析与字节码生成

  • 语义分析:对结构上正确的源程序进行上下文逻辑审查。
    • 标注检查:包括变量使用前是否已被声明、变量与赋值之间的数据类型是否可以匹配等。
    • 数据及控制流分析:对程序上下文逻辑进行更进一步的验证,包括局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否全部的受查异常都被正确处理等。
  • 解语法糖:虚拟机运行时并不支持语法糖的语法,所以,须要在编译阶段还原回简单的基础语法结构。
  • 字节码生成:把前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘中,同时还进行了少许的代码添加和转换工做。

二、Java 语法糖

  • 泛型与类型擦除:泛型的本质是参数化类型的应用,即将所操做的数据类型指定为一个参数。
  • 自动装箱与拆箱、遍历循环、变长参数。
  • 条件编译:编译器在编译时只对知足条件的代码进行编译,而将不知足条件的代码舍弃。Java 语言可使用条件为布尔常量值的 if 语句进行条件编译。

8、晚期(运行期)优化

一、HotSpot 虚拟机内的即时编译器

(1)解释器与编译器

  • 当程序须要迅速启动和执行时,解释器能够首先发挥做用,省去编译的时间,当即执行。
  • 在程序运行后,随着时间的推移,编译器把愈来愈多的代码编译成本地代码后,能够获取更高的执行效率。

(2)C一、C2 编译器

  • C1 编译器(Client Compiler):运行在 Client 模式。
  • C2 编译器(Server Compiler):运行在 Server 模式。

(3)混合模式、解释模式与编译模式

  • 混合模式:解释器与编译器搭配使用的方式。
  • 解释模式:所有代码都使用解释方式执行,编译器彻底不介入工做。
  • 编译模式:优先采用编译方式执行,可是解释器仍会在编译没法进行时介入执行过程。

(4)分层编译

分层编译根据编译器编译、优化的规模与耗时,划分出不一样的编译层次。微信

  • 第 0 层:程序解释执行,解释器不开启性能监控功能,可触发第 1 层编译。
  • 第 1 层:也称 C1 编译,将字节码编译为本地代码,进行简单、可靠的优化,必要时加入性能监控的逻辑。
  • 第 2 层(或 2 层以上):也称 C2 编译,也是将字节码编译为本地代码,但会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

二、即时编译触发条件

(1)热点代码

  • 被屡次调用的方法。
  • 被屡次执行的循环体。

(2)热点探测

判断一段代码是否是热点代码,是否是须要触发即时编译,这样的行为称为热点探测。数据结构

  • 基于采样的热点探测:虚拟机周期性地检查各个线程的栈顶,若是发现某个方法常常出如今栈顶,那这个方法就是“热点代码”。
  • 基于计数器的热点探测:虚拟机为每一个方法(甚至是代码块)创建计数器,统计方法的执行次数,若是执行次数超过必定阈值就认为它是“热点代码”。

HotSpot 虚拟机使用的是基于计数器的热点探测方法,它为每一个方法准备了两类计数器。多线程

  • 方法调用计数器:统计方法被调用的次数。
  • 回边计数器:统计一个方法中循环体代码执行的次数。

三、编译优化技术

  • 公共子表达式消除:若是一个表达式 E 已经计算过了,而且从先前计算到如今 E 中全部变量的值都没有变化,那么 E 的此次出现就成了公共子表达式。对于这种表达式,没有必要再次进行计算,直接用前面计算过的表达式结果代替 E 便可。
  • 数组边界检查消除:编译器经过数据流分析断定数组下标是否会越界,若是分析后肯定不会越界,那么能够把数组的上下界检查消除。
  • 方法内联:把目标方法的代码“复制”到发起调用的方法之中,避免发生真实的方法调用。
  • 逃逸分析:当一个对象在方法中定义后,若是它被外部方法所引用或被外部线程访问到,那么就说这个对象发生了逃逸。若是一个对象不会逃逸到方法或线程以外,那么能够为这个变量进行一些高效的优化,好比栈上分配、同步消除、标量替换等。

9、Java 内存模型与线程

一、Java 内存模型

(1)主内存与工做内存

  • 全部的变量都存储在主内存中。每条线程有本身的工做内存,工做内存中保存了被该线程使用到的变量的主内存副本拷贝。
  • 线程对变量的操做必须在工做内存中进行,而不能直接读写主内存中的变量。
  • 不一样的线程之间没法直接访问对方工做内存中的变量,线程间变量值的传递须要经过主内存来完成。

(2)内存间交互操做

  • lock(锁定):把一个主内存变量标识为一条线程独占的状态。
  • unlock(解锁):把一个处于锁定状态的主内存变量释放出来。
  • read(读取):把一个变量的值从主内存传输到线程的工做内存中,以便随后的 load 动做使用。
  • load(载入):把 read 操做从主内存中获得的变量值放入工做内存的变量副本中。
  • use(使用):把工做内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个须要使用到变量的值的字节码指令时将会执行这个操做。
  • assign(赋值):把一个从执行引擎接收到的值赋给工做内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操做。
  • store(存储):把工做内存中一个变量的值传送到主内存中,以便随后的 write 操做使用。
  • write(写入):把 store 操做从工做内存中获得的变量的值放入主内存的变量中。

(3)volatile 的做用

  • 保证变量对全部线程的可见性。
  • 禁止指令重排序优化。

(4)原子性、可见性与有序性

  • 原子性
    • 基本数据类型的访问读写具有原子性: Java 内存模型直接保证了 read、load、assign、use、store 和 write 操做的原子性。
    • synchronized 代码块之间的操做具有原子性:底层经过 lock 和 unlock 操做实现。
  • 可见性:当一个线程修改了共享变量的值,其余线程可以当即得知这个修改。Java 内存模型经过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存做为传递媒介的方式来实现可见性。
  • 有序性:若是在本线程内观察,全部的操做都是有序的;若是在一个线程中观察另外一个线程,全部的操做都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”现象和“工做内存与主内存同步延迟”现象。

(5)先行发生原则

  • 程序次序规则:在一个线程内,按照程序代码顺序,书写在前的操做先行发生于书写在后的操做。准确地说,是控制流顺序而不是程序代码顺序,由于要考虑分支、循环等结构。
  • 管程锁定规则:一个 unlock 操做先行发生于后面(时间上的前后顺序)对同一个锁的 lock 操做。
  • volatile 变量规则:对一个 volatile 变量的写操做先行发生于后面(时间上的前后顺序)对这个变量的读操做。
  • 线程启动规则:Thread 对象的 start() 方法先行发生于此线程的每个动做。
  • 线程终止规则:线程中的全部操做都先行发生于对此线程的终止检测。
  • 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程检测到中断事件的发生。
  • 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
  • 传递性:若是操做 A 先行发生于操做 B,操做 B 先行发生于操做 C,那么能够得出操做 A 先行发生于操做 C。

二、Java 与线程

(1)线程的实现

  • 使用内核线程实现:内核线程就是直接由操做系统内核支持的线程。
  • 使用用户线程实现:用户线程彻底创建在用户空间的线程库上,系统内核不能感知线程的存在。
  • 使用用户线程加轻量级进程混合实现:用户线程仍是彻底创建在用户空间中,而操做系统提供支持的轻量级进程则做为用户线程和内核线程之间的桥梁。

(2)Java 线程调度

  • 协同式线程调度:线程的执行时间由线程自己来控制,线程执行完以后,主动通知系统切换到另一个线程上。
  • 抢占式线程调度:每一个线程由系统来分配执行时间,线程的切换不禁线程自己来决定。

Java 使用的线程调度方式就是抢占式调度。并发

(3)线程状态

  • 新建(New):线程建立后还没有启动。
  • 运行(Runable):包括了操做系统线程状态中的 Running 和 Ready,处于此状态的线程有可能正在执行,也有可能正在等待着 CPU 为它分配执行时间。
  • 无限期等待(Waiting):不会被分配 CPU 执行时间,等待着被其余线程显式地唤醒。
  • 限期等待(Timed Waiting):不会被分配 CPU 执行时间,无须等待被其余线程显式地唤醒,在必定时间以后会由系统自动唤醒。
  • 阻塞(Blocked):线程被阻塞了,在等待着获取到一个排他锁。在程序等待进入同步区域的时候,线程将进入这种状态。
  • 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

10、线程安全与锁优化

一、Java 语言中的线程安全

按线程安全的“安全程度”由强至弱排序,能够将多个线程的共享数据分为 5 类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。

  • 不可变:不可变的对象必定是线程安全的,不管是对象的方法实现仍是方法的调用者,都不须要再采起任何的线程安全保障措施。
  • 绝对线程安全:必须知足“无论运行时环境如何,调用者都不须要任何额外的同步措施”。
  • 相对线程安全:就是咱们一般意义上所讲的线程安全,它须要保证对一个对象单独的操做是线程安全的,可是对于一些特定顺序的连续调用,则须要在调用端使用额外的同步手段来保证调用的正确性。
  • 线程兼容:对象自己并非线程安全的,但能够经过在调用端正确地使用同步手段来保证对象在并发环境中能够安全地使用。
  • 线程对立:不管调用端是否采起了同步措施,都没法在多线程环境中并发使用的代码。

二、线程安全的实现方法

  • 互斥同步(阻塞同步):同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用,而互斥是实现同步的一种手段。
  • 非阻塞同步:在进行同步操做时,不须要把线程挂起,而是先进行操做,若是没有其余线程争用共享数据,那操做就成功了;若是共享数据有争用,产生了冲突,那就采起其余的补偿措施。
  • 无同步方案
    • 可重入代码(纯代码):若是一个方法的返回结果是能够预测的,只要输入了相同的数据,就都能返回相同的结果,那它就知足可重入性的要求,固然也就是线程安全的。
    • 线程本地存储:若是能保证使用共享数据的代码在同一个线程中执行,那么就能够把共享数据的可见范围限制在同一个线程以内。这样,无须同步也能保证线程之间不出现数据争用的问题。

三、锁优化

  • 自旋锁:若是物理机有多个处理器,能让多个线程同时并行执行,那么可让后面请求锁的线程“稍等一下”,但不放弃处理器的执行时间,而后看看持有锁的线程是否很快就会释放锁。为了让线程等待,只需让线程执行一个忙循环(自旋),这就是所谓的自旋锁。
  • 锁消除:锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,可是被检测到不可能存在共享数据竞争的锁进行消除。
  • 锁粗化:若是一系列的连续操做都对同一个对象反复加锁和解锁,甚至加锁操做是出如今循环体中的,那么虚拟机将会把加锁同步的范围扩展(粗化)到整个操做序列的外部,这样只须要加锁一次就能够了。
  • 轻量级锁:轻量级锁并非用来代替重量级锁的,而是在没有多线程竞争的前提下,减小传统的重量级锁使用操做系统互斥量产生的性能消耗。对象头的 Mark Word 有个锁标志位,用于标识同步对象的锁状态。
  • 偏向锁:偏向锁是指这个锁会偏向于第一个得到它的线程,若是在接下来的执行过程当中,该锁没有被其余线程获取,则持有偏向锁的线程将永远不须要再进行同步。

相关文章

《深刻理解 Java 虚拟机》读书笔记:Java 内存区域与内存溢出异常
《深刻理解 Java 虚拟机》读书笔记:垃圾收集器与内存分配策略
《深刻理解 Java 虚拟机》读书笔记:虚拟机性能监控与故障处理工具
《深刻理解 Java 虚拟机》读书笔记:类文件结构
《深刻理解 Java 虚拟机》读书笔记:虚拟机类加载机制
《深刻理解 Java 虚拟机》读书笔记:虚拟机字节码执行引擎
《深刻理解 Java 虚拟机》读书笔记:早期(编译期)优化
《深刻理解 Java 虚拟机》读书笔记:晚期(运行期)优化
《深刻理解 Java 虚拟机》读书笔记:Java 内存模型与线程
《深刻理解 Java 虚拟机》读书笔记:线程安全与锁优化

交流区


微信公众号:惊却一目
我的博客:惊却一目

相关文章
相关标签/搜索