* 内存中的运行数据区域分为以下几部分
* 线程私有的3种
* 程序计数器 用于切换线程时恢复现场
* java虚拟机栈 每个方法运行时创建一个栈帧,存储局部变量表,操作数栈,动态链接,方法出口灯
* 本地方法栈 存储本地方法
* 线程共享的
* java堆 几乎所有的对象实例都会在这里分配内存
* 方法区 存储被虚拟机加载的类信息,常量(static),静态变量(final),JIT编译后的代码
* 常量池(存在于方法区中) 存储字面量和符号引用
* 直接内存 NIO
* 对象创建过程
* 首先在常量池中能否找到对应类所需的符号引用,并且检查这个符号引用是否被加载,解析和初始化
* 如果类没有加载,则先进行加载过程
* 加载后为新生对象分配空间(这个空间是可以确定的)
* CAS方式分区
* TLAB(在线程的分配缓冲中分配) hotspot使用这个
* 设置对象的头元素,包括hash值,GC分代,锁指针等等
* 对象的访问定位两种方式(从栈指向指向方法区的方式)
* 句柄
* 直接指针
* 内存溢出
* java堆溢出 通过dump出快照进行检查
* 查看是否是内存泄漏,如果是通过GCroot查看引用链,找到泄漏对象
* 查看是否是内存溢出,如果是溢出调整堆内存大小
* 虚拟机栈和本地方法栈溢出
* 方法区和运行时常量池溢出
* 小知识点String.intern(str)在1.6时会在常量区中插如str,而在1.7之后
在该字符串对象已存在时,会插入第一次出现这个字符串的对象引用
* 直接内存溢出 在nio操作较多时会发生
* 如何判断对象已死
* 引用计数器法,给对象添加引用计数器,有引用时加一.
* 优点,实现简单,效率高
* 缺点,无法解决循环引用的情况
* 可达性分析法,从GCroot对象开始向下搜索,搜索的路径称为引用链,不在引用链上的对象说明没有被引用
* GC Roots对象有以下四种
* 虚拟机栈中栈帧中的本地变量表中引用的对象,即native方法
* 方法区中类的静态属性引用的对象
* 方法去中类的常量引用的对象
* 本地方法栈中JNI引用的对象
* 生存还是死亡(两次标记过程)
* 第一次发现对象没有被引用链引用,将被标记并判断是否有必要执行finalize()
* 如果已被虚拟机调用过finalize(),或者没有负载finalize()则被判定成没必要执行
* 否则被判定成有必要执行finalize()
* 被判定有必要的对象放到F-queue队列中, 并由一个低优先级的Finalizer线程去执行finalize()方法,
* 在执行过程中,虚拟机会对队列中的对象进行二次标记
* 此时如果被判断有对象被引用,则被移除队列,否则被回收.
* 所有对象的finalize()方法只会被执行一次
* 回收方法区
* 废弃常量和无用的类可以被回收,但不是一定被回收,通过参数控制虚拟机回收
* 废弃常量,没有任何对象引用这个常量.
* 无用的类,同时满足以下三个条件
* 该类的所有实例已被回收
* 加载该类的classload被回收
* 该类对象Class对象没被引用,无法通过反射得到该类.
* 垃圾回收算法
* 标记-清除
* 效率低
* 空间碎片多
* 复制算法 分成两块
* 标记-整理 存活的对象往一个方向移动
* 分代收集 根据不同年代使用不同回收算法
* 新年代 使用复制算法
* 老年代 使用标记-清除或者标记-整理
* HotSpot中对垃圾回收算法实现的一些细节
* 枚举根节点
* 不需要遍历整个方法区得到GCroot,一般通过准确性GC算法,oopMap得到这些对象引用所在的地址
* 安全点
* 安全点指需要很长时间执行的代码块(如循环,调用,异常等),需要所有线程都跑到安全点时才中断然后进行GC
* 抢占式中断:先停下所有线程,然后把没到安全点的线程继续执行到安全点
* 主动式中断:设置一个标志,各个线程运行时主动轮询这个标志,当标志显示为真时,线程中断,轮询标志是与安全点重合的,即中断的时候就是安全点
* 安全区 指在这个区域内中断都是安全的,进入和出去的时候检查是否完成了根节点枚举.
* 解决线程在sleep等状态下无法响应jvm请求.
* 垃圾回收器
* Serial 单线程收集,GC时中断所有线程,简单而高效,适用于client
* ParNew Serial的多线程版本.多线程进行垃圾收集.其他与Serial一样
* Paraller Scavenge 关注吞吐量的收集器,通过调整新生代空间之类的实现的,该收集器可以自动调整堆中内存分配比例
* Serial old Seria收集器的老年版本.使用标记-整理算法
* Paraller old 标记-整理算法 与Paraller Scavenge配合成吞吐量收集器组
* CMS 标记-清除算法
* 初始标记 标记GCroots 能直接关联的对象
* 并发标记 可以和工作线程一起进行
* 重新标记 标记并发标记中变动的一些对象的标记
* 并发清除 可以和工作线程一起进行
* 优点 并发收集,低停顿
* 缺点
* CPU占用率高,
* 因为浮动垃圾(并行收集时产生的垃圾)的存在,不能等内存满了再进行收集
* 标记-清除算法导致空间碎片多
* G1
* 充分利用多核进行并发
* 把整个堆分成多个region.每个region分属于老年代和青年代,建立停顿时间预测模型.收集价值最大的region
* GC分成两种
* 新生代GC minorGC
* 老年代GC fullGC
* 内存分配策略.不是一定的,取决与垃圾回收器
* 新生代分为 一个eden两个survivor,默认比例8比1比1
* 新对象建立到eden中
* minorGC一次还存活就放到survivor中
* 老年代
* 分配策略
* 大多数对象分配到eden中,eden内存不够则发起一次minorGC
* 当eden中内存以不够新对象加入时,将Eden中的一个大对象分配担保到老年代后再分配.
* 大对象直接到老年代.由于新生代是复制算法回收,所以大对象会产生很多复制消耗.
* 长期存活放到老年代
* 达到设置的标准年龄
* 达到动态年龄:某些对象的加一起的内存大于survivor的一半
* 空间分配担保判断老年代空间是否能接受新生代传过来的对象
* 老年代的连续空间大于新生代的对象总大小
* 老年代的连续空间大于历次晋升的平均大小进行minorGC,否则进行fullGC
小知识点 引用
* jps 虚拟机进程状况工具
* jstat虚拟机统计信息监视工具
* jinfo java配置信息工具
* jmap java内存映射工具
* jhat 虚拟机堆转储快照分析工具
* jstack java堆栈跟踪工具
* HSDISl jit生成代码反汇编
* jConsolr 和VisualVM 可视化工具
* 关于作者遇到的一些jvm问题进行调参
* 对eclips进行调参实验
* 一些调参命令
* Class文件中文件结构,按顺序排列
* 0-4 CAFEBABY
* 4-8 版本号
* 常量池
* 2字节 常量池容量计数值,标志常量池中有多少常量
* 常量池中主要存放两大类常量,每一项的入口都是一个表数据,表明常量的类型,长度,和数据
* 字面量
* 如字符串,final常量,还有八种基本类型都实现了常量池技术
* 符号引用 这些符号不经过运行期无法得到真正的内存地址
* 类和接口的全限定名
* 字段的名称和描述符
* 方法的名称和描述符
* 21种自动生成的常量:如方法的参数的个数和类型,返回值的类型.
* 2字节 访问标志
* 标记方法所属的类或接口的修饰符,如是否是public,abstract,final,enum等等
* 索引,父类索引,接口索引的集合 都是u2(2字节无符号数) 类
* 用于确定类的继承关系
* 父类索引只有一个
* 接口索引有多个,入口有标志位代表索引的数量
* 字段表集合
* 描述接口或者类中声明的变量,包括类级变量和实例级变量,但是不包括方法内部声明的局部变量
* 其内部都是标志位(boolean型),对于字段名称和字段的数据类型需要引用常量值中的常量确定.
* 字段的修饰符(public)
* 是实例变量还是类变量(static)
* 可变性(final)
* 并发可见性(volatie)
* 可序列化(transient)
* 字段类型索引,索引常量池
* 字段描述索引,索引常量池
* 方法表集合,类似于字段表
* 方法的定义
* 方法字节码Code属性表索引
* 小知识,方法的特征签名包扩,方法名称,参数顺序,参数类型,而方法重载是依据方法的特征签名不同决定的
* 属性表集合
* Class文件,字段表,方法表都可以携带自己的属性文件,以用于描述某些场景专有的信息.其中的每个属性的名称都需要从常量值中引一个常量来表示,基础的如常量池中的21种自动生成常量
*属性:
* Code属性
* javac得到的字节码指令,存储在Code属性中,其中包含,指向常量池的名称索引,所需操作数栈的深度,局部变量所需的存储空间等等
* 第一个位置会留一个slot存放当前实例对象的引用用以支持this的实现
* Exceptions属性
* 表示方法throws关键字后列举的异常
* LineNumBerTable属性
* 描述java源码和字节码行数之间的对应关系
* LocalVariableTable属性
* 栈帧中局部变量表中的变量与java源码重定义的变量直线的关系
* SourceFile属性
* 记录生成这个Class文件的源码文件名称
* ConstantValue
* 通知虚拟机自动为静态变量赋值
* 等等
* 字节码指令简介
* 类加载过程
* 加载-->验证-->准备-->解析-->初始化-->使用-->卸载
* 解析过程出现的时机并不确定,这是为了支持java语言的运行时绑定
* 类的加载时机并未强制确定
* 初始化时间
* new ,读取或者设置静态变量(被final static修饰的除外,已在编译器放入常量池的静态字段除外),调用静态方法时
* 对类进行反射调用时
* 初始化一个类时,如果其父类未初始化,则要初始化其父类
* 虚拟机启动时,初始化main方法类
* invoke执行方法时
注:调用父类静态方法,子类不初始化,调用类中常量不初始化.
对于接口,调用子接口,父接口不一定初始化
* 加载
*过程需要三步
* 通过一个类的全限定名来获取此类的二进制流,可有多种方式获取,如jar包war包等等
* 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
* 在内存中生成一个代表这个类的java.lang.Class对象
* 加载之后就将二进制流存储到方法区内
* 验证 是连接的第一步
* 验证Class文件是正确的
* 准备
* 为类变量(static修饰的)分配内存并设置类变量初始值(系统确定的初始值,如int=0)
* 仅类变量初始化,而实例变量在实例初始化的时候直接在内存中分配
* 被final static修饰的类变量(常量)会直接赋真实值
* 解析
* 将常量池中的符号引用替换成直接引用的过程
* 初始化
* 初始化类变量为程序员要求的值
* 执行clinit,clinit=类变量赋值操作+静态语句块,并且其执行顺序是按照类中定义顺序确定的,并且父类的clinit先执行
* 类加载器
* 加载类的加载器和类本身确定其在虚拟中的独立地位
* 如:比较两个类相等,必须这两个类来源于同一个类加载器,包括equal,instanceof等等
* 加载策略
* 双亲委派模型
* 所有的类加载时都先交给父类加载器加载,父类在其搜索空间搜索不到才给子类加载.
* 优点:使类有层级,保证一些类由特定的加载器加载,如object永远由启动类加载器加载.
* 破坏双薪委派模型
* OGSi每个程序模块属于一个加载器,换一个程序模块就换连同类加载器一起换掉.实现热替换
* 运行时栈结构
* 一个线程有一个方法栈,一个方法在栈中有一个栈帧,每次执行栈最顶部的栈帧,执行完弹出
* 每个栈帧包含方法的局部变量表,操作数栈,动态链接,方法返回等信息.
* 局部变量表
* 组成
* 第0位当前方法的实例对象(this) ,这个this指的是实际类型
* 从1开始的slot是方法的参数
* 之后根据方法内部变量定义顺序和作用域分配
* 存放变量的Slot是可以复用的.即其失效时他占用的slot会被别的变量占用
* GC时在作用域内的变量或者占用的slot没有被别的占用,即这个slot还没有被读写过时,不会被GC
* 所以以后写代码时,大的变量用完要赋值为null;尽量以恰当的变量作用域控制GC
* 局部变量不像类变量在准备阶段会赋初值,局部变量只在初始化阶段赋值,所以其没有被赋值是不会有初始值的,会报错
* 操作数栈
* 控制需要操作的数
* 和局部变量表有一部分重叠,可以避免复制
* 动态链接
* 指向常量池该帧所属方法的引用
* 方法返回地址,回到开始调用的位置
* return
* 异常
* 附加信息
* 调试相关
* 方法调用
* 确定调用哪一个方法
* 所有的目标方法都只是常量池中的符号引用,需要将其转化成直接引用
* 解析过程中转化
* 所有编译器就可以确定所调用的方法版本,且运行期不会改变(不会多态)
* 类的静态方法
* 类的私有方法
* 实例构造器
* 父类方法
* 分派转化
* 静态分派(重载的本质)
* Human man = new Man();//human是man的父类
* Human称为对象的静态类型,Man称为对象的实际类型
* 静态类型的是编译时可知的
* 静态类型也是可变的,如强制转换(Man)man,但是这种转换在编译时也是可知的
* 实际类型变化的结果是运行时才可知.编译器编译时并不知道对象的实际类型是什么
* 实际类型变化Human man = new Man();这种转换编译期不可知
* 在使用重载时,参数类型的不同是根据静态类型而确定的,而不是实际类型.
* 重载时会根据静态类型选择最合适的重载方法执行
* 如参数为char的,如果没有char参数的重载版本,会选择int\string\long等等执行
* 越上层的父类执行的优先级越低,总是选择最接近的版本执行
* 动态分配(重写的本质)
* 运行时代码时,运行到invokevitual指令,就在运行先按照操作数栈所指对象找到对象的实际类型
* 再按照实际类型分派方法,查找方法区,如果能找到对应方法,就将符号引用解析到对象的实际类型上
* 找不到对应方法就从继承关系从下往上寻找描述符和简单名称都相符的方法执行
* 再执行invokevitual指令就再次进行解析,所以可以把一个符号引用解析到不同的直接引用上,从而实现重写,即运行期根据实际类型确定执行方法版本.
* 单分派和多分派
* 定义
* 宗量 --方法的参数和方法的接收类型称为宗量
* 根据一个宗量进行分派的称为单分配,根据多个分派的称为多分派
* java的静态分派属于多分派
* 重载要考虑静态类型和方法参数
* java的动态分派属于单分派
* 重写时只考虑方法的接受者的实际类型
* 虚拟机实现动态分派---虚方法表(方法区)
* 正常方式应该是在运行期寻找合适方法,但是太消耗内存
* 真正的虚拟机中的虚方法表是在链接过程中随着类变量的初始化而初始化
* 过程
* 建立一个虚方法表,表中存贮子类实际指向的方法入口地址
* 子类未重写的方法,指向父类
* 子类重写的方法,指向子类
* 类加载的连接阶段初始化
* 动态语言
* 动态语言是类型检查是在运行期
* java不是,java的类型检查在编译期
* java7的invoke包
* java基于栈的字节码解释引擎
* 基于栈的指令集,便于移植,但是比寄存器方式要慢
* Tomcat:正统的类加载器架构
* java服务器的类加载器需要实现以下功能
* 服务器中的每个web应用的类库可以相互隔离
* 服务器中的每个web应用的类库可以相互共享
* 服务器需要的类库与应用程序的类库可以隔离
* 实现jsp的热替换(hotSwap)功能
* tomcat的类加载器结构
* ![](http://i.imgur.com/EzZRhr4.png)
* Conmmon加载器加载服务器和web应用都可以共享的类库
* Catalina加载器加载tomcat依赖的类库
* Shared加载应用间共享的类库
* WebApp加载每个应用隔离的类库
* jsp加载器加载jsp文件,用于支持热交换,用完丢弃.每次加载重新新建一个jsp加载器
* tomcat6之后吧common、catalina、shared类加载器加载的类都用CommonClassLoad加载,从lib目录下加载.
* OGSI加载
* javac编译器
* 编译过程
* 解析与填充符号表的过程
* 插入式注解处理器的注解处理过程
* 分析与字节码生成过程
* javac编译动作的入口是com.sun.tools.javac.main.javaCompiler类
* 解析与填充符号表
* 词法分析
* 语法分析
* java语法糖的味道
* 泛型与类型擦除
* 编译后字节码中泛型是擦除的,即运行时arraylis和arraylist是一样的
* 不能够根据参数泛型的不同来进行重载
1.编译时泛型被擦除
2.判断是否能够重载要求的是方法具备不同的方法签名,所以参数泛型不同,而且返回值不用的两个方法是可以存在在一个类中的
所以需要根据泛型类型不同进行重载时,可以把返回值改成不同的方式
* 泛型擦除只是把Code属性中的泛型擦掉了,但是其元数据中还存在泛型数据,所以可以通过反射手段获得参数化类型的根本依据
* 自动拆箱装箱
* 1.包装类的==运算符在不遇到算数运算时不会进行自动拆箱
* 2.包装类的equals()方法不处理数据转型
* 条件编译
while(false){
XXX
}
* 条件编译的实验方式是if
* java编译时会把条件为布尔常量值为false的代码块擦除
* 所以上面会报错
* java中的条件语句都必须在方法内实现,所以无法用条件语句更改java类的结构
* jit
* 编译器和解释器,机器码和字节码可以相互转化
* 什么是热点代码
* 被多次调用的方法
* jit编译
* 被多次执行的方法体
* OSR编译,方法中循环体循环次数多,会把整个方法编译
* 热点探测器
* 基于采样的热点探测,某些方法经常出现在栈顶
* 基于计数器(hotspot用这种)
* 计数器热度衰减
* 超过一定时间,其方法调用次数仍不足以提交给jit,就会衰减一半
* 回边计数器计算OSR编译
* 循环体执行的次数
* 根据回边计数器和回边计数器之和和回边计数器阈值比较.
* 编译过程
* 初始通过解释方式运行
* 达到阈值后线程等待,进行jit,编译完之后,执行机器码
* 编译的第一个过程 字节码--->高级中间代码(HIR)
* 编译的第二个过程 高级中间码--->低级中间代码
* 编译的第三个过程 低级中间代码--->机器码(通过线性扫面算法进行)
* 可以通过-XX: +printCompilation要求jit时把被编成机器码的方法名称打印出来
* 反汇编
* jit代码优化
* 方法内联 去掉方法调用的成本
* 冗余代码消除,例如消除复制中的中间值
* 复写传播 两个一样的变量用一个代替
* 无用代码消除 永远不会被执行的代码
* 公共子表达式消除 如果一个表达式E已经被计算过了,并且之后没有改变,那么E的这次出现就成了公共子表达式
* 数组边界检查消除
*
* 方法内联
* 逃逸分析
* 什么是
* 方法中的对象当做参数传递到别的方法中
* 方法中的对象被别的方法引用
* 如果能判断方法中没有对象逃逸可以进行优化
* 栈上直接给这个对象分配内存
* 消除同步
* 标量替换,将这个对象替换成一组标量的集合
* C++静态编译器和java即使编译器比较
* jit占用用户程序运行时间
* 虚拟机要经常进行动态安全检查
* java中虚方法使用频率高
* 动态扩展语言,运行时加载新的类可能会更改类之间的继承关系,一些全局优化难以进行
* java的内存分配固定,c++比较灵活
* java开发效率高
* 要协调内存和各个硬件之间的读取速度差异
* java内存模型
* 主内存,线程共有
* 线程间变量值的传递都在主内存中进行
* 这个变量不包括局部变量和方法参数,因为他们存在栈内,没有安全的问题
* 线程工作内存,线程私有
* 保存该线程用到的变量的拷贝副本
* 线程对变量的所有操作(读取、赋值等等)都在工作内存中进行
* 内存间的交互操作8种(都是原子的),用于将变量从主内存复制到工作内存和从工作内存同步到主内存
* lock,锁定,将一个变量标识成线程独占
* ulock,解锁,解锁后的变量才能被别的线程锁定
* read,读取
* load,加载
* user,使用
* assgin,赋值
* store,赋值,把工作内存中的一个变量值传到主内存中
* write,写入,将store得到的值放入主内存的变量中
* volatile 最轻量级的同步机制
* 作用
* 所有线程的立即可见的,每次使用都能将最新值取到操作数栈,但之后对这个数进行的操作时就不能保证是最新的
* 所有的volatile变量的修改都立即刷进内存,任何变量读取前都从主存刷新变量值
* 禁止指令重排,设置一个内存屏障
* 不符合一下条件,必须要用synchronized进行同步
* 运算结果不依赖变量的当前值,或能确保只有单一线程修改变量的值
* 变量不需要与其他的状态量共同参与不变约束
* 对long和double的操作一般的虚拟机都保证成原子的
* 原子性
* 有序性
* 可见性
* 先发先行原则
* 虚拟机内定义的一些明确的指定执行顺序
* java与线程
* 线程实现,java虚拟机一般是1:1
* 用户线程
* 内核线程
* java线程调度
* 协同式
* 一个线程执行完再执行另一个
* 抢占式
* 线程的执行直线由系统分配,线程的切换不由线程本身决定(可以用thread.yield()让出执行时间,但是不能获得)
* 可以有不同的优先级
* windows优先级推进器,越勤奋的线程分得的线程越多
* 线程状态和转换
* 新建 new
* 运行
* running 正在运行
* ready 等待CPU分配时间
* 无限期等待 waiting,这种状态下cpu不会给分配时间,需要等待notify/notifyall唤醒
* 没有设置Timeout的wait
* 没有设置Timeout的join
* locksupport.park
* 限期等待 这种状态CPU也不会分配时间,但是无需被唤醒,只要时间到了就自动唤醒
* sleep(),占着cpu睡觉
* 设置timeout的Object.wait()
* 设置timeout的Thread.join()
* 阻塞(blocked) 阻塞和等待的区别是阻塞再等待排他锁
* 结束
* 线程安全分成以下5类
* 不可变,final修饰的基本类型
* 常见不可变对象有枚举,String,以及Long等数字包装类,BigInteger,BigeDecimal等等
* String类型的replace等方法都不会改变Stirng对象的值,而会创建一个新的String对象
* 将对象中所有带状态的值都声明成final,这样生成对象后,它就是不可变的
* 绝对线程安全
* 不管运行环境如何,调用者都不用额外增加同步操作,一般标明的线程安全都不是绝对线程安全的
* 相对线程安全 通常意义上的线程安全
* Vector,hsahtable等等中的方法如get,remove等等是安全的,一般的操作不用加额外的同步,但是一些特定顺序的操作还是要进行同步操作
* 线程兼容,指线程本身不安全,但是可以使用同步手段使其在并发过程中可以安全使用,如hashmap等等
* 线程队里,无论怎么样,都无法在同步过程中使用
* 线程安全的实现方法
* 互斥同步,Syschronized
* 保证同一时刻数据只能一个线程使用
* 阻塞和唤醒都要系统将线程从用户态转到核心态,非常耗时间
* 还有一个ReenTranLock也可以实现互斥同步
* 非阻塞同步
* CSA操作
*先判断X是否等于初值Y,等于则赋值成C,否则继续尝试判断
* AtomicInteger 中的incrementAndGet是原子的,其使用CSA不断尝试加1
* 无同步方案,同步消除
* 可重入代码就不用同步
* 可以随时中断执行,或者继续执行,而不会出现错误
* 原理
* 不依赖堆等公共资源,用到的状态量都有参数中转入,不调用非可重入方法,即一个方法只要输入值确定,其输出值是确定的
* 线程本地存储
* 锁优化
* 自旋锁和自适应性自旋锁
* 优点,省了切换线程状态的消耗
* 缺点,占用cpu资源
* 自适应是指,如果虚拟机判断这个对象刚被自旋等待成功过,就把自旋时间增加,否则省略自旋操作
* 锁消除
* 检测到不存在竞争的数数据,就不用加锁
* 主要通过逃逸进行分析,堆上的数据不会逃逸就不用加锁
* 锁粗化
* 一般情况下同步块尽量越小越好
* 当有一系列操作频繁对锁进行释放和获取时,就应该将锁扩大到整个操作序列
* 轻量级锁
* 在没有竞争时,减少传统重量级锁使用操作系统互斥量的性能消耗
* 在对象头中添加指向锁的标志
* 如果同步对象没被锁定(01), 虚拟机在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象的头文件拷贝
* 然后虚拟机尝试将对象头指向Lock Record,使用CAS
* 指向成功就获得锁执行
* 指向失败
* 如果头文件已直线lock Record,则直接进去执行
* 否则,说明头文件已指向别的线程的栈帧,则获取失败,轻量级锁膨胀成重量级锁
* 这种做法的依据是,大部分的锁,在同步周期内是不会发生竞争的
* 膨胀成重量级锁,由于额外的CAS操作,所以效率更低
* 偏向锁
* 与轻量级锁相比,连CAS操作都不做,去掉所有同步操作
* 偏向的意思是偏向于第一个获得他的线程,如果接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永不同步
* 过程
* 如果当前虚拟机为偏向锁模式
* 锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为01,即偏向模式
* 同时使用CAS将获得这个锁的ID记录在对象的头中,
* 如果CAS成功,则持有该锁的线程进入该锁相关的同步快时,虚拟机都不再进行任何同步操作
* 当有另一个线程尝试访问时,偏向模式失败,根据锁对象目前是否处于被锁定的状态,撤销偏向后恢复到未锁定状态或者轻量级锁状态
* 如果程序中大多数锁总是被多个线程同时访问,则偏向模式是多余的