1. Java对象头
HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头是对象在内存中的元数据信息,它包含了对象的一些重要信息,如对象的锁状态、类元数据指针、数组长度等。对象头是Java虚拟机(JVM)管理和操作对象的关键部分。
Java对象头通常包括以下信息:
Mark Word(标记字):Mark Word是对象头中最重要的部分,占据了对象头的大部分空间。它包含了对象的哈希码、锁信息、垃圾回收信息等。Mark Word的具体结构因JVM实现而异,通常包括以下内容:
对象标志位:用于标识对象的状态,例如是否被锁定、是否已经被回收等。
哈希码:用于支持hashCode方法,以及在哈希表中查找对象的位置。
分代年龄:用于垃圾回收的分代算法,标记对象所属的年代。
锁信息:用于支持同步操作,包括偏向锁、轻量级锁和重量级锁的状态信息。
类型指针:类型指针指向对象的类元数据,它用于确定对象所属的类,以支持对象的方法调用和字段访问。
数组长度(仅对数组对象有效):如果对象是数组,对象头中会包括数组长度信息。
对象头的结构和大小可能会因不同的JVM实现和对象类型而异。对象头通常占用一定的内存空间,因此它会增加每个对象的内存开销。对象头的信息对于垃圾回收、同步和虚拟机运行时的对象操作非常重要。其中Mark Word结构如下图所示。
2. Synchronized锁优化
jdk1.6之前,synchronized属于重量级锁(悲观锁) ,jdk1.6之后被进行了大幅度优化,支持锁升级制度缓解加锁和解锁造成的性能费,锁的级别采用: 偏向锁 -> 轻量级锁 -> 重量级锁。
原子性: synchronized依靠两个字节码指令monitorenter和monitorexit,可以保证被synchronized修饰的代码在同一时间只能被一个线程访问
可见性: JMM(Java内存模型)规定,内存主要分为主内存和工作内存两种,每个线程拥有不同的工作内存,线程工作时会从主内存中拷贝一份变量到工作内存中。代码执行后,有时工作内存中的变量无法及时刷新到主内存中,或者工作内存无法及时获取主内存的最新值,导致共享变量在不同线程间处于不可见性,由此JMM对synchronized做了2条规定:
a.线程解锁前,必须把变量的最新值刷新到主内存中。
b.线程加锁时,先清空工作内存中的变量值,从主内存中重新获取最新值到工作内存中
.有序性: 有时候编译器和处理器为了提升代码效率,会进行指令重排序,但是as-if-serial规定无论怎么重排序,单线程程序的执行结果都不能被改变,而synchronized保证了被修饰的程序在同一时间内只能被同一线程访问, 所以其也算是保证了有序性,但synchronized实际上并不是禁止了被修饰的代码指令重排序。
2.1 偏向锁
偏向锁的思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时,Mark Word的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作即可再次获取锁,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以对于没有锁竞争的场合,偏向锁有很好的优化效果,但是在有多线程竞争锁的场合,偏向锁就失效了,这种场合下不应该使用偏向锁,否则会得不偿失,偏向锁失败后,将会优先升级为轻量级锁 基本工作过程:
当线程A访问代码块并获取锁对象时,会通过CAS在Mark Word中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后再次获取锁的时候,需要比较当前线程的 threadID 和 Mark Word 中的threadID是否一致,如果一致,则无需使用CAS来加锁、解锁;如果不一致,则是因为有其他线程如线程b 来竞争该锁,而偏向锁时不会主动释放锁,因此 Mark Word 存储的还是 线程a 的threadID,那么需要查看 Mark Word 中记录的 线程a 是否存活,如果没有存活,那么锁对象被重置为无锁状态,线程b 可以竞争将其设置为偏向锁;如果存活,那么立刻查找线程a的栈帧信息,如果还是需要继续持有这个锁, 那么暂停当前线程a,撤销偏向锁,升级为轻量级锁,如果 线程不再使用该锁,那么将锁状态设为无锁状态,重新偏向新的线程。
在java中偏向锁是默认开启的,绝大多数 情况下,对于加锁的程序大多都会有两个以上的线程去竞争,如果开启偏向锁,反而会加剧锁的资源消耗,可以通过jvm参数启动或关闭偏向锁:
-XX:-UseBiasedLocking = false //偏向锁的启动延迟默认为5秒,可以取消这个延迟: XX:BiasedLockingStartUpDelay=0
2.2 轻量级锁
当多个线程竞争同一个锁时,偏向锁将转化为轻量级锁。轻量级锁通过CAS(Compare and Swap)操作来尝试获取锁,而不会让线程阻塞。如果CAS操作成功,线程获得锁;否则,线程将升级为重量级锁。轻量级锁是由偏向锁升级而来,它考虑的情况是竞争锁的线程不多,而且线程持有锁的时间也不长的情景。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”。轻量级锁加锁流程如下图所示。
2.3 重量级锁
当轻量级锁无法满足需求,多个线程仍然竞争同一个锁时,锁会升级为重量级锁。在这种情况下,线程将阻塞,直到获取锁。
2.4 锁消除
锁消除是一种Java虚拟机(JVM)和编译器进行的优化技术,用于自动消除不必要的锁操作,以提高程序的性能。锁消除通常用于优化多线程程序中的同步操作。如果编译器确定某个锁的作用域在程序中是局限的,即锁只在某个特定的线程范围内使用,那么它就有可能被消除。代码示例如下:
public class test { public static void main(String[] args) { int count=0; for(int i=0;i<100;i++){ Object ob=new Object(); synchronized (ob){ count++; } } } }
2.5 各种锁对比
参考: