JVM 内置锁 synchronized 关键字,偏向锁优化中的批量重偏向和批量撤销(上)

简介: JVM 内置锁 synchronized 关键字,偏向锁优化中的批量重偏向和批量撤销

批量重偏向和批量撤销


从偏向锁的加锁和解锁的过程中可以看出,当只有一个线程反复进入同步代码块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获取锁的时候,就需要等到 safe point 时,再将偏向锁撤销为无锁的状态或者升级为轻量级锁,会消耗一定的性能,所以在多线程竞争频繁的情况下,偏向锁不仅不能提升性能,还会导致性能下降,于是,有了批量重偏向与批量撤销的机制。


以 class  为单位,每个class 维护一个偏向锁撤销计数器,每一次该 class 的对象发生偏向撤销操作时,该计数器 +1 ,当这个值达到重偏向阈值(默认 20 )时,JVM 就认为该 class 的偏向锁有问题,因此会进行批量重偏向。


每个 class 对象会对应一个 epoch 字段,每个处于偏向锁状态的对象的 mark word 也有该字段,其初始值为创建该对象的 class 的 epoch 的值,每次发生批量重偏向时,就将该值 +1 ,同时遍历 JVM 中所有线程的栈,找到该 class 所有正处于加锁状态的偏向锁,将其 epoch 字段更新为新值,下次获得锁时,发现该对象的  epoch 值和 class 的 epoch 值不想等,那就算当前已经偏向了其他的线程,也不会执行撤销操作,而是直接通过 CAS将其 Mark word 的 thread id 修改为当前线程 id


当达到重偏向阈值(默认 20)后, 假设该 class 计数器继续增长,当其达到批量撤销的阈值(默认40) JVM 就认为该 class 的使用场景存在多线程竞争,会标记当前 class 不可偏向,之后,对于该 class 的锁,直接走轻量级锁的逻辑。


运用场景(解决的问题):


1、批量重偏向(bulk rebias) 机制:一个线程创建了大量对象并且执行了初始的同步操作,后来另外一个线程也来将这些对象作为锁进行操作,这样会导致大量的偏向锁撤销操作。


2、批量撤销(bulk revoke) 机制:在明显多线程竞争剧烈的场景下使用偏向锁时不合适的。


Java 系统默认参数查询


java -XX:+PrintFlagsFinal


我们可以通过-XX:BiasedLockingBulkRebiasThreshold-XX:BiasedLockingBulkRevokeThreshold 来手动设置阈值。默认值如下:


image.png


批量偏向锁重偏向


实验代码如下:


public class A1 {
    public static void main(String[] args) throws Exception {
        //延时产生可偏向对象
        Thread.sleep(5000);
        //创造100个偏向线程t1的偏向锁
        List<A> listA = new ArrayList<>();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                A a = new A();
                synchronized (a) {
                    listA.add(a);
                }
            }
            try {
                //为了防止JVM线程复用,在创建完对象后,保持线程t1状态为存活
                Thread.sleep(100000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        //睡眠3s钟保证线程t1创建对象完成
        Thread.sleep(3000);
        System.out.println("打印t1线程,list中第20个对象的对象头:");
        System.out.println((ClassLayout.parseInstance(listA.get(19)).toPrintable()));
        //创建线程t2竞争线程t1中已经退出同步块的锁
        Thread t2 = new Thread(() -> {
            //这里面只循环了30次!!!
            for (int i = 0; i < 30; i++) {
                A a = listA.get(i);
                synchronized (a) {
                    //分别打印第19次和第20次偏向锁重偏向结果
                    if (i == 18 || i == 19) {
                        System.out.println("第" + (i + 1) + "次偏向结果");
                        System.out.println((ClassLayout.parseInstance(a).toPrintable()));
                    }
                }
            }
            try {
                Thread.sleep(10000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t2.start();
        Thread.sleep(3000);
        System.out.println("打印list中第11个对象的对象头:");
        System.out.println((ClassLayout.parseInstance(listA.get(10)).toPrintable()));
        System.out.println("打印list中第26个对象的对象头:");
        System.out.println((ClassLayout.parseInstance(listA.get(25)).toPrintable()));
        System.out.println("打印list中第41个对象的对象头:");
        System.out.println((ClassLayout.parseInstance(listA.get(40)).toPrintable()));
    }
}


我们一起来看看结果,在 thread0 中创建的第 20 个共享锁对象,当前是偏向锁,线程 id -594241531


image.png


再来看看 t2 线程,前19次均产生了轻量级锁,到了20次时候到达批量重偏向的阈值20 ,此时不是轻量级锁,而变成了偏向锁,此时偏向锁线程 t2 , 线程 id : -619083515


image.png


再看看偏向锁结束后的对象头信息。 前 20 个对象并没有触发批量重偏向机制,线程 t2 释放锁之后,变成无锁状态;


第 20 - 30 个对象,触发了批量重偏向机制,对象为偏向锁状态,偏向线程 t2, 线程 t2 的 id 为-619083515


第 31 个对象之后,也没有触发了批量重偏向机制,对象依然偏向线程 t1 , 线程 t1  的 id 是  -594241531


image.png


注意:这里建议共享对象为自定义类对象

相关文章
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
444 27
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
1327 166
|
监控 Java 编译器
聊聊JVM如何优化
JVM的优化是一个复杂而细致的过程,涉及内存管理、垃圾回收、即时编译、线程调度等多个方面。通过合理配置JVM参数、选择合适的垃圾回收器、优化线程调度和使用专业的监控工具,可以大幅提升Java应用的性能和稳定性。掌握这些优化技巧,能够帮助开发者在高并发、高负载的生产环境中保持系统的高效运行。
767 13
|
存储 设计模式 监控
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
384 0
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
|
NoSQL Java Redis
秒杀抢购场景下实战JVM级别锁与分布式锁
在电商系统中,秒杀抢购活动是一种常见的营销手段。它通过设定极低的价格和有限的商品数量,吸引大量用户在特定时间点抢购,从而迅速增加销量、提升品牌曝光度和用户活跃度。然而,这种活动也对系统的性能和稳定性提出了极高的要求。特别是在秒杀开始的瞬间,系统需要处理海量的并发请求,同时确保数据的准确性和一致性。 为了解决这些问题,系统开发者们引入了锁机制。锁机制是一种用于控制对共享资源的并发访问的技术,它能够确保在同一时间只有一个进程或线程能够操作某个资源,从而避免数据不一致或冲突。在秒杀抢购场景下,锁机制显得尤为重要,它能够保证商品库存的扣减操作是原子性的,避免出现超卖或数据不一致的情况。
438 10
|
Rust 安全 Java
JVM原理与实现——Synchronized关键字
在多线程Java程序中,`Synchronized`关键字用于确保线程安全。本文深入探讨其工作原理,通过分析字节码`monitorenter`和`monitorexit`,解释JVM如何实现同步机制。文章展示了`Synchronized`方法的编译结果,并详细解析了轻量锁和重度锁的实现过程,包括Mark Word的状态变化及CAS操作的应用。最后简要介绍了`ObjectMonitor::enter()`函数在获取重度锁时的作用。
337 0
JVM原理与实现——Synchronized关键字
|
监控 Java 开发者
Java虚拟机(JVM)深度优化指南####
本文深入探讨了Java虚拟机(JVM)的工作原理及其性能优化策略,旨在帮助开发者通过理解JVM的内部机制来提升Java应用的运行效率。不同于传统的技术教程,本文采用案例分析与实战技巧相结合的方式,为读者揭示JVM调优的艺术。 ####
524 8
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
268 5
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
存储 IDE Java
实战优化公司线上系统JVM:从基础到高级
【11月更文挑战第28天】Java虚拟机(JVM)是Java语言的核心组件,它使得Java程序能够实现“一次编写,到处运行”的跨平台特性。在现代应用程序中,JVM的性能和稳定性直接影响到系统的整体表现。本文将深入探讨JVM的基础知识、基本特点、定义、发展历史、主要概念、调试工具、内存管理、垃圾回收、性能调优等方面,并提供一个实际的问题demo,使用IntelliJ IDEA工具进行调试演示。
333 0