一:java对象内存布局
对象在堆内存的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data) 和对齐填充
二:对象在堆内存中的存储布局
1:对象头 (在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节)
对象标记(Mark Word):默认存储对象的HashCode、分代年龄和锁标志等信息。
类元信息:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象哪个类的实例
2:实例数据
存放类的属性(Field)数据信息,包括父类的属性信息
3:对齐填充
保证8个字节的倍数
三:Sychronized的锁升级
Synchronized用的锁是存在Java对象头里的MarkWord中,锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位
无锁:
偏向锁:MarkWord存储的是偏向的线程ID
轻量锁:MarkWord存储的是指向线程栈中Lock Record的指针
重量锁:MarkWord存储的是指向堆中的monitor对象(系统互斥量指针)
四:无锁
五:偏向锁
1:偏向锁的持有?
单线程竞争,当线程A第一次竞争到锁时,通过修改MarkWord中的偏向线程ID、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步。
2:偏向锁的撤销?
当有另外一个线程逐步来竞争锁的时候,就不能再使用偏向锁了,要升级为轻量级锁,竞争线程尝试CAS更新对象头失败,同时检查持有偏向锁的线程是否还在执行:
(1)第一个线程正在执行Synchronized方法(处于同步块),它还没有执行完,其他线程来抢夺,该偏向锁会被取消掉并出现锁升级,此时轻量级锁由原来持有偏向锁的线程持有,继续执行同步代码块,而正在竞争的线程会自动进入自旋等待获得该轻量级锁
(2)第一个线程执行完Synchronized(退出同步块),则将对象头设置为无所状态并撤销偏向锁,重新偏向。
3:案例说明
public class SynchronizedUpDemo {
public static void main(String[] args) {
/**
* 这里偏向锁在JDK6以上默认开启,开启后程序启动几秒后才会被激活,可以通过JVM参数来关闭延迟 -XX:BiasedLockingStartupDelay=0
*/
try {
TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
o = new Object();
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
六:轻量级锁
1:什么是轻量级锁?
多线程竞争,但是任意时候最多只有一个线程竞争,即不存在锁竞争太激烈的情况,也就没有线程阻塞。
2:轻量级锁升级到 重量级锁
自旋一定程度和次数(Java8 之后是自适应自旋锁------意味着自旋的次数不是固定不变的
(1)线程如果自旋成功了,那下次自旋的最大次数会增加,因为JVM认为既然上次成功了,那么这一次也大概率会成功
(2)如果很少会自选成功,那么下次会减少自旋的次数甚至不自旋,避免CPU空转
七:重量级锁
1:有大量线程参与锁的竞争,冲突性很高
2:重量级锁原理
3:代码演示
八:常见面试题
1:轻量锁和偏向锁的区别:
(1)争夺轻量锁失败时,自旋尝试抢占锁
(2)轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁,线程不会主动释放偏向锁
2:为什么每个对象都能作为一个锁?
java对象 是天生的Monitor,每一个对象都有成为Monitor的潜质,因为在Java设计中,每一个Java对象自打娘胎里出来就带一个看不见的锁,它叫内部锁或者Monitor锁
3:Monitror与java对象以及线程如何关联?
4:锁升级后,hashcode去哪儿了?
5:什么是锁消除?
/**
/**
* @Author:xiaoqi
* @creat 2023/8/12 11:27
* 锁消除
* 从JIT角度看想相当于无视他,synchronized(o)不存在了
* 这个锁对象并没有被共用扩散到其他线程使用
* 极端的说就是根本没有加锁对象的底层机器码,消除了锁的使用
*/
public class LockClearUpDemo {
static Object object = new Object();
public void m1() {
//锁消除问题,JIT会无视它,synchronized(o)每次new出来的,都不存在了,非正常的
Object o = new Object();
synchronized (o) {
System.out.println("-----------hello LockClearUpDemo" + "\t" + o.hashCode() + "\t" + object.hashCode());
}
}
public static void main(String[] args) {
LockClearUpDemo lockClearUpDemo = new LockClearUpDemo();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
lockClearUpDemo.m1();
}, String.valueOf(i)).start();
}
}
}
/**
* -----------hello LockClearUpDemo 229465744 57319765
* -----------hello LockClearUpDemo 219013680 57319765
* -----------hello LockClearUpDemo 1109337020 57319765
* -----------hello LockClearUpDemo 94808467 57319765
* -----------hello LockClearUpDemo 973369600 57319765
* -----------hello LockClearUpDemo 64667370 57319765
* -----------hello LockClearUpDemo 1201983305 57319765
* -----------hello LockClearUpDemo 573110659 57319765
* -----------hello LockClearUpDemo 1863380256 57319765
* -----------hello LockClearUpDemo 1119787251 57319765
*/
6:什么是锁粗化?
/**
* @Author:xiaoqi
* @creat 2023/8/12 12:27
* 锁粗化
* 假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器会把这几个synchronized块合并为一个大块
* 加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提高了性能
*/
public class LockBigDemo {
static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (objectLock) {
System.out.println("111111111111");
}
synchronized (objectLock) {
System.out.println("222222222222");
}
synchronized (objectLock) {
System.out.println("333333333333");
}
synchronized (objectLock) {
System.out.println("444444444444");
}
//底层JIT的锁粗化优化
synchronized (objectLock) {
System.out.println("111111111111");
System.out.println("222222222222");
System.out.println("333333333333");
System.out.println("444444444444");
}
}, "t1").start();
}
}