怎么查出这个网站是谁做的,谷歌首页,网站视频封面怎么做,下载河北公众号官方版安装上一篇文章#xff0c;简要介绍了syn的基本用法和monter对象的结构#xff0c;本篇主要深入理解#xff0c;偏向锁、轻量级锁、重量级锁的本质。
对象内存布局
Hotspot虚拟机中#xff0c;对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据 (Instance Da…上一篇文章简要介绍了syn的基本用法和monter对象的结构本篇主要深入理解偏向锁、轻量级锁、重量级锁的本质。
对象内存布局
Hotspot虚拟机中对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据 (Instance Data)和对齐填充(Padding)。
对象头:比如 hash码对象所属的年代对象锁锁状态标志偏向锁(线程)ID 偏向时间数组长度(数组对象才有)等。 实例数据:存放类的属性数据信息包括父类的属性信息; 对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存 在的仅仅是为了字节对齐。
对象头详解
HotSpot虚拟机的对象头包括:
Mark Word 用于存储对象自身的运行时数据如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等这部分数据的长度在32位和64位的虚拟机中分别为 32bit和64bit官方称它为“Mark Word”。Klass Pointer 对象头的另外一部分是klass类型指针即对象指向它的类元数据的指针虚拟机通过这个指 针来确定这个对象是哪个类的实例。 32位4字节64位开启指针压缩或最大堆内存32g时4字 节否则8字节。jdk1.8默认开启指针压缩后为4字节当在JVM参数中关闭指针压缩(-XX:- UseCompressedOops)后长度为8字节。数组长度(只有数组对象有) 如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度。 4字节 如何查看java对象信息数据 dependencygroupIdorg.openjdk.jol/groupIdartifactIdjol-core/artifactIdversion0.9/version/dependencypublic static void main(String[] args) {Object o new Object();System.out.println(ClassLayout.parseInstance(o).toPrintable());}可以发现一个Object在压缩情况下是16字节。对其填充4字节。 OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12 4 (loss due to the next object alignment)思考题markword如何记录锁状态的 这里有一个问题那就是为什么需要锁升级 在于原来的syn的锁比较重每次wait\notify都需要从用户态到内核态的转换。而利用偏向锁和轻量级锁可以在用户态没有竞争或者两个线程竞争的前提下进行锁竞争避免频繁切换到内核态。你看本质上还是为了提高锁的性能。
偏向锁
偏向锁是一种针对加锁操作的优化手段经过研究发现在大多数情况下锁不仅不存在多线程竞争而且总是由同一线程多次获得因此为了消除数据在无竞争情况下锁重入(CAS操 作)的开销而引入偏向锁。对于没有锁竞争的场合偏向锁有很好的优化效果。
偏向锁延迟
偏向锁模式存在偏向锁延迟机制:HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建 的对象开启偏向锁模式。JVM启动时会进行一系列的复杂活动比如装载配置系统类初始化等 等。在这个过程中会使用大量synchronized关键字对对象加锁且这些锁大多数都不是偏向锁。 为了减少初始化时间JVM默认延时加载偏向锁。 //关闭延迟开启偏向锁
‐XX:BiasedLockingStartupDelay0 3 //禁止偏向锁‐XX:‐UseBiasedLocking
//启用偏向锁
‐XX:UseBiasedLockingSystem.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(new Object()).toPrintable());TimeUnit.SECONDS.sleep(4);System.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(new Object()).toPrintable()); 偏向锁 public static void main(String[] args) throws InterruptedException {TimeUnit.SECONDS.sleep(4);Object obj new Object();new Thread(()-{System.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(obj).toPrintable());synchronized (obj) {System.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(obj).toPrintable());}System.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(obj).toPrintable());},T1).start();**流程**可以发现当一个对象刚被创建的时候markword处于无锁状态并随即进入偏向锁状态此时mark word字段中的threadI为0此时线程获取某个对象的syn关键字的时候会发现这个对象时处于可偏向的101的。并且threadId为0.就会通过cas原子操作竞争这个偏向锁。markword为5 101 如果cas成功则将当前线程的id设置进去。 但是发现在执行完syn代码块的时候发现并没有退出偏向锁。原因在于偏向锁不会主动释放主要是提高加锁的效率。当这个线程再次获取syn对象锁的时候可以判断markword是否偏向以及thredid是不是自己的线程id 做处理如果是的话直接可以使用。并且CAS操作其实是硬件层面的操作
轻量级锁
在一个线程获取锁的时候会设置为偏向锁但是当两个线程交替执行的时候会从偏向锁升级为轻量级锁。轻量级锁所适应的场景是线程交替执行同步块的场合如果存在同一时间多个线程访问同一把锁的场合就会导致轻量级锁膨胀为重 量级锁。 TimeUnit.SECONDS.sleep(4);Object obj new Object();new Thread(()-{System.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(obj).toPrintable());synchronized (obj) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(obj).toPrintable());}},T1).start();Thread.sleep(1000);new Thread(()-{synchronized (obj) {System.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(obj).toPrintable());}System.out.println(Thread.currentThread().getName()\tClassLayout.parseInstance(obj).toPrintable());},T2).start();从图中可以看出锁一开始是可偏向线程T1cas获取到偏向锁但是线程T2没有获取到自己CAS失败偏向锁不会主动释放锁因此在升级偏向锁时虚拟机需要暂停持有偏向锁的线程查看是否还在使用这个偏向锁如果不在使用那么就会将markword设置为无锁状态如果这个锁还在使用那就升级为轻量级锁。
好了我们来思考几个问题 为什么需要在将偏向锁升级时需要暂停偏向锁 这是因为虚拟机需要根据持有偏向锁的线程是否正在使用偏向锁来决定将偏向锁转为无锁还是轻量级锁其实检查这类也是复合操作。因为是两个不同的线程操作虚拟机线程可能检查到没有使用偏向锁但是过了一会线程获取到偏向锁。显然无法将偏向锁设置为无锁状态所以需要暂停持有偏向锁的线程。如何暂停就直接使用gc中的STWstop the word
能否不把偏向锁线程状态会退回偏向状态 其实如果出现线程竞争会退到可偏向状态可能会导致频繁的STW所以还不如回退到无锁状态。 市面上很多的资料都是锁升级就是无锁-偏向锁-》轻量级锁-重量级锁。 其实是不准确的。正确的其实是一开始是偏向锁状态根据偏向锁的是否持有线程判断如果没有持有就升级到无锁状态如果持有锁并且还有一个线程竞争的前提下那就升级到轻量级锁。如果竞争更加激烈升级到重量级锁。但是升级到重量级锁后是无法降级的。 重量级锁
在上述代码的基础上在加一个线程获取就可以获得此效果。
锁消除
除了syn的锁升级syn还有两个优化那就是锁消除和锁粗化虚拟机在JIT编译时会根据代码的分析去掉没有必要的锁在多线程操作的安全性StringBuffer中的append函数 设计实现时加了锁在下面代码中strBuffer是局部变量不会被多线程共享更不后在多线程环境下调用它的append函数append函数的锁就可以被优化消除。
锁粗化
锁组化其实也是在JIT编译的时候根据锁的范围将多个小的锁范围调整为一个。比如如下就是将1W次的append加解锁粗化成一次。
总结