Java锁机制(三)对象的内存布局|锁升级

对象的内存布局

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

  • Mark Word(标记字段):其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。
  • Class Pointer(Class对象指针):Class对象指针的大小是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址
  • 对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节。(如果有的话)
  • 对齐填充:最后一部分是对齐填充的字节,使得总内存为8的倍数。

JVM(hotspot 64位)对象头内部组成

1. 图解

2. 偏向锁、轻量级锁和重量级锁在对象头中的标识:其中2bit的锁标志位表示锁的状态,1bit的偏向锁标志位表示是否偏向。

1.无锁状态

2.偏向锁状态

3.打印对象

typescript 复制代码
    public class ClassLayoutDemo {

        public static void main(String[] args) {
            //构建了一个对象实例
            ClassLayoutDemo classLayoutDemo = new ClassLayoutDemo();
            System.out.println(ClassLayout.parseInstance(classLayoutDemo).toPrintable());
        }
    }

    引入依赖:
<!--classLayout:帮助打印对象的布局-->
  <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.9</version>
  </dependency>

打印结果:

python 复制代码
com.gupao.gupaoedu.example.ClassLayoutDemo object internals:
OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
//Mark Word
        0     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)
        //Klass Pointer
        8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
        12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

一共是16个字节,其中对象头是12个字节,还有4个对齐字节(因为64位虚拟机规定:对象的大小必须是8的倍数),由于这个对象里没有任何属性和方法,所以对象的实例数据为0个字节,如果添加一个boolean字段

python 复制代码
com.gupao.gupaoedu.example.ClassLayoutDemo object internals:
OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
//Mark Word
        0     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)
        //Klass Pointer
        8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
        12     1 boolean ClassLayoutDemo.happy
        13     3        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

从上面的输出来看,我们很容易发现,整个对象的大小没有改变,依然是16个字节,其中对象头12个字节,boolean字段 happy 占1个字节,剩下的3个Byte是对齐字节。由此我们可以发现一个对象的布局可以粗略的分为3个部分:对象头(Object Header),对象的实例数据(Instance Data)和 对齐字节(Padding),也叫对齐填充。

各种数据类型大小:

对象类型 字节
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8
引用类型 开启指针压缩为4,不开启为8
普通对象头 开启指针压缩为12,不开启为8
数据对象头 开启指针压缩为16,不开启为24

指针压缩

JVM最初是32位的,随着64位系统的兴起,JVM也迎来了从32位到64位的转换,32位的JVM对比64位的内存容量比较有限。但是使用64位虚拟机的同时,带来一个问题,64位下的JVM中的对象指针占用内存会比32位的多1.5倍(这是因为对象指针在64位架构下,长度会翻倍(更宽的寻址),对于那些将要从32位平台移植到64位的应用来说,平白无辜多了1/2的内存占用),这是我们不希望看到的。于是在JDK1.6时,引入了指针压缩。

JVM参数

ruby 复制代码
*-XX:+UseCompressedClassPointers 参数:**启用类指针(类元数据的指针)压缩。 
**-XX:+UseCompressedOops 参数:**启用普通对象指针压缩。Oops缩写于:ordinary object pointers

在Jdk1.8中默认开启,可用命令进行检测:

ruby 复制代码
java -XX:+PrintCommandLineFlags -version

结果:

ruby 复制代码
    wangwang@localhost ~ % java -XX:+PrintCommandLineFlags -version
    -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags 
    -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
    java version "1.8.0_261"
    Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
    Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)
    wangwang@localhost ~ %

    参数中的+号代表开启参数,-号代表关闭参数。

测试

csharp 复制代码
public class ClassLayoutDemo {
    //    -XX:-UseCompressedClassPointers -XX:-UseCompressedOops -XX:+PrintCommandLineFlags
    char ch = 'c';
    public static void main(String[] args) {
        //构建了一个对象实例
        ClassLayoutDemo classLayoutDemo = new ClassLayoutDemo();
        System.out.println("-----------------");
        System.out.println(ClassLayout.parseInstance(classLayoutDemo).toPrintable());
    }
}

开启指针压缩(默认)

python 复制代码
om.gupao.gupaoedu.example.ClassLayoutDemo object internals:
OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
        0     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)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
        12     2   char ClassLayoutDemo.ch                        c
        14     2        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

未开启指针压缩

python 复制代码
com.gupao.gupaoedu.example.ClassLayoutDemo object internals:
OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
        0     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)                           80 c0 35 10 (10000000 11000000 00110101 00010000) (271958144)
        12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
        16     2   char ClassLayoutDemo.ch                        c
        18     6        (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total

锁消除

锁消除是指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程调用,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然无须再进行。

比如下面一段代码:

typescript 复制代码
public static String concatString(String str1, String str2, String str3) {
    StringBuffer sb = new StringBuffer();
    sb.append(str1).append(str2).append(str3);
    return sb.toString();
}

大家都熟知StringBuffer是一个线程安全的字符串拼接类,它的每个方法都加了synchronized关键字,每个方法都需要获取锁才能执行,锁对象就是StringBuffer的实例化对象。上述代码中,锁对象就是sb实例对象,经过虚拟机的逃逸分析后会发现sb对象的作用域仅仅被局限在concatString方法内部,根本不会被外部方法使用或调用。因此,其他线程完全没有机会访问到它,也不会产生资源竞争的同步问题。在解释执行时,这里仍然会加锁,在经过服务端编译器的即时编译后(因为逃逸分析是属于即时编译器的优化技术),这段代码就会忽略所有的同步措施而直接执行。

锁升级

上代码:

csharp 复制代码
import org.openjdk.jol.info.ClassLayout;

    public class JOLDemo3 {
        static A a;

        public static void main(String[] args) {
            a = new A();
            System.out.println("before lock");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());
            sync();
            System.out.println("after lock");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());
        }

        public static void sync(){
            synchronized (a){
                System.out.println("locking");
                System.out.println(ClassLayout.parseInstance(a).toPrintable());
            }
        }
    }

打印:

vbnet 复制代码
before lock
org.jlfang.concurrency.lock.A object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
  0     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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
        12     1   boolean A.happy                                   true
        13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

locking
org.jlfang.concurrency.lock.A object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
  0     4           (object header)                           80 f5 6c 03 (10000000 11110101 01101100 00000011) (57472384)
        4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
        8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
        12     1   boolean A.happy                                   true
        13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

after lock
org.jlfang.concurrency.lock.A object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
  0     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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
        12     1   boolean A.happy                                   true
        13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total


Process finished with exit code 0

打印出来的结果表明这个对象再上锁过程中(locking)变成了轻量级锁(00)的状态,这显然不符合我们的预期。提到过在有且仅有1个线程获取synchronized关键字中的对象时,该对象的锁状态为偏向锁。那么为什么会这样呢?因为JVM的偏向锁有延迟启动机制,每个对象并不会在一开始就获得偏向锁,需要在程序运行后大概4-5秒之后才会获得。同样,让我们通过demo来验证一下

相同的代码,我们只是加了5秒的睡眠时间(避免偏向锁启动延迟的方法,还有更加直接的办法,我们可以在启动时添加下列参数取消偏向锁启动延迟:

ruby 复制代码
-XX:+UseBiasedLocking
-XX:BiasedLockingStartupDelay=0

我们在代码中添加:

arduino 复制代码
    public static void main(String[] args) throws InterruptedException {
        //睡眠5秒,保证偏向锁启动
        TimeUnit.SECONDS.sleep(5);
        a = new A();
....

打印:

vbnet 复制代码
before lock
org.jlfang.concurrency.lock.A object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
  0     4           (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
        4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
        8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
        12     1   boolean A.happy                                   true
        13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

locking
org.jlfang.concurrency.lock.A object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
  0     4           (object header)                           05 38 97 02 (00000101 00111000 10010111 00000010) (43464709)
        4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
        8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
        12     1   boolean A.happy                                   true
        13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

after lock
org.jlfang.concurrency.lock.A object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
  0     4           (object header)                           05 38 97 02 (00000101 00111000 10010111 00000010) (43464709)
        4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
        8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
        12     1   boolean A.happy                                   true
        13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total


Process finished with exit code 0

终于,我们看到了偏向锁状态101,需要注意的是,和轻量级锁和重量级锁每次退出同步将变成无锁状态不同,当偏向锁退出同步状态后,该对象依然保持了偏向状态。

锁升级整体流程

  1. 当对象初始化后,还未有任何线程来竞争,此时为无锁状态。其中锁标志位为01,偏向锁标志位为0
  1. 当有一个线程来竞争锁,锁对象第一次被线程获取时,锁标志位依然为01,偏向锁标志位会被置为1,此时锁进入偏向模式。同时,使用CAS操作将此获取锁对象的线程ID设置到锁对象的Mark Word中,持有偏向锁,下次再可直接进入。
  1. 此时,线程B尝试获取锁,发现锁处于偏向模式,但Mark Word中存储的不是本线程ID。那么线程B使用CAS操作尝试获取锁,这时锁是有可能获取成功的,因为上一个持有偏向锁的线程不会主动释放偏向锁。如果线程B获取锁成功,则会将Mark Word中的线程ID设置为本线程的ID。但若线程B获取锁失败,则会执行下述操作。
  1. 偏向锁抢占失败,表明锁对象存在竞争,则会先撤销偏向模式,偏向锁标志位重新被置为0,准备升级轻量级锁。首先将在当前线程的帧栈中开辟一块锁记录空间(Lock Record),用于存储锁对象当前的Mark Word拷贝。然后,使用CAS操作尝试把锁对象的Mark Word更新为指向帧栈中Lock Record的指针,CAS操作成功,则代表获取到锁,同时将锁标志位设置为00,进入轻量级锁模式。若CAS操作失败,则进入下述操作。
  1. 刚一出现CAS竞争轻量级锁失败时,不会立即膨胀为重量级锁,而是采用自旋的方式,自旋锁在JDK1.4.2中引入。不断重试,尝试抢锁。默认自旋10次。如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。JDK1.6中,默认开启自旋,可通过下面参数更改自旋次数。JDK1.6对于只能指定固定次数的自旋进行了优化,采用了自适应的自旋,重试机制更加智能。
diff 复制代码
-XX:PreBlockSpin

    自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
    如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,
    进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,
    直接阻塞线程,避免浪费处理器资源
  1. 只有通过自旋依然获取不到锁的情况,表明锁竞争较为激烈,不再适合额外的CAS操作消耗CPU资源,则直接膨胀为重量级锁,锁标志位设置为10。在此状态下,所有等待锁的线程都必须进入阻塞状态。

hashcode与锁

32位,Mark Word的存储结构如下:

64位下,Mark Word的存储结构如下:

由此可知,在无锁状态下,Mark Word中可以存储对象的identity hash code值。当对象的hashCode()方法(非用户自定义)第一次被调用时,JVM会生成对应的identity hash code值,并将该值存储到Mark Word中。后续如果该对象的hashCode()方法再次被调用则不会再通过JVM进行计算得到,而是直接从Mark Word中获取。只有这样才能保证多次获取到的identity hash code的值是相同的(以jdk8为例,JVM默认的计算identity hash code的方式得到的是一个随机数,因而我们必须要保证一个对象的identity hash code只能被底层JVM计算一次)。

我们还知道,对于轻量级锁,获取锁的线程栈帧中有锁记录(Lock Record)空间,用于存储Mark Word的拷贝,官方称之为Displaced Mark Word,该拷贝中可以包含identity hash code,所以轻量级锁可以和identity hash code共存;对于重量级锁,ObjectMonitor类里有字段可以记录非加锁状态下的mark word,其中也可以存储identity hash code的值,所以重量级锁也可以和identity hash code共存。

对于偏向锁,在线程获取偏向锁时,会用Thread ID和epoch值覆盖identity hash code所在的位置。如果一个对象的hashCode()方法已经被调用过一次之后,这个对象还能被设置偏向锁么?答案是不能。因为如果可以的话,那Mark Word中的identity hash code必然会被偏向线程Id给覆盖,这就会造成同一个对象前后两次调用hashCode()方法得到的结果不一致。因此偏向锁不能与hashcode共存

HotSpot VM的锁实现机制是:

  • 当一个对象已经计算过identity hash code,它就无法进入偏向锁状态;
  • 当一个对象当前正处于偏向锁状态,并且需要计算其identity hash code的话,则它的偏向锁会被撤销,并且锁会膨胀为轻量级锁或者重量锁;
  • 轻量级锁的实现中,会通过线程栈帧的锁记录存储Displaced Mark Word;重量锁的实现中,ObjectMonitor类里有字段可以记录非加锁状态下的mark word,其中可以存储identity hash code的值。

偏向锁、轻量级锁和重量级锁的性能对比

为什么要这么关注synchronized关键字维护的到底时哪种锁呢?各个锁之间的差异很大吗?用demo来测试一下。

我们调用同步方法10亿次,来计算10次++操作所耗的时间:

首先是偏向锁,特别注意启动时要加上参数,否则启动的将是轻量级锁

ruby 复制代码
    开启偏向锁:-XX:+UseBiasedLocking ‐XX:BiasedLockingStartupDelay=20000
    关闭偏向锁:-XX:+UseBiasedLocking ‐XX:BiasedLockingStartupDelay=0
//为了测试,也可以设置睡眠时间,让偏向锁延时启动

代码:

arduino 复制代码
public class JOLDemo5 {
    public static void main(String[] args) throws Exception {
        //睡眠5秒,保证偏向锁启动
        //TimeUnit.SECONDS.sleep(5);
        A a = new A();
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000L; i++) {
            a.parse();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

public class A {
    int i;
    public synchronized void parse(){
        i++;
    }
}

偏向锁结果:

python 复制代码
    com.gupao.gupaoedu.example.mine.entity.A object internals:
    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
            0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
            4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
            8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
            12     4    int A.i                                       0
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

218ms
    com.gupao.gupaoedu.example.mine.entity.A object internals:
    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
            0     4        (object header)                           05 90 00 76 (00000101 10010000 00000000 01110110) (1979748357)
            4     4        (object header)                           e5 7f 00 00 (11100101 01111111 00000000 00000000) (32741)
            8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
            12     4    int A.i                                       100000000
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

    可以看到锁标志为偏向锁:101

再看轻量级锁的结果,我们只需要把偏向锁延迟启动加上就能模拟出来轻量锁的效果。

轻量级锁结果:

python 复制代码
    com.gupao.gupaoedu.example.mine.entity.A object internals:
    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
            0     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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
            12     4    int A.i                                       0
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

1802ms
    com.gupao.gupaoedu.example.mine.entity.A object internals:
    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
            0     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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
            12     4    int A.i                                       100000000
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

    我们可以看到锁标志位001(虽然标志仍未偏向锁,是由于main线程有一些其他的线程)

重量级锁:

arduino 复制代码
public class JOLDemo6 {
    static CountDownLatch countDownLatch = new CountDownLatch(10000000);

    public static void main(String[] args) throws InterruptedException {
        final A a = new A();

        long start = System.currentTimeMillis();

        for(int i=0;i<2;i++){
            new Thread(()->{
                while(countDownLatch.getCount()>0){
                    a.parse();
                }
            }).start();
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

重量级锁结果:

python 复制代码
    com.gupao.gupaoedu.example.mine.entity.A object internals:
    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
            0     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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
            12     4    int A.i                                       0
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

6653ms
    com.gupao.gupaoedu.example.mine.entity.A object internals:
    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
            0     4        (object header)                           fa db 81 b9 (11111010 11011011 10000001 10111001) (-1182671878)
            4     4        (object header)                           e5 7f 00 00 (11100101 01111111 00000000 00000000) (32741)
            8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
            12     4    int A.i                                       100000001
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

比较结果:

偏向锁 轻量级锁 重量级锁
执行1亿次++操作耗时(单位:毫秒) 218 1802 6653

比较可知:偏向锁,轻量级锁和重量级锁在性能上有着巨大的差异,如何合理的利用synchronized关键字是十分重要的。

相关推荐
camellias_1 小时前
SpringBoot(二十三)SpringBoot集成JWT
java·spring boot·后端
tebukaopu1481 小时前
springboot如何获取控制层get和Post入参
java·spring boot·后端
昔我往昔1 小时前
SpringBoot 创建对象常见的几种方式
java·spring boot·后端
灭掉c与java1 小时前
第三章springboot数据访问
java·spring boot·后端
啊松同学2 小时前
【Java】设计模式——工厂模式
java·后端·设计模式
枫叶_v2 小时前
【SpringBoot】20 同步调用、异步调用、异步回调
java·spring boot·后端
源码12154 小时前
ASP.NET MVC宠物商城系统
后端·asp.net·宠物
nameofworld4 小时前
前端面试笔试(二)
前端·javascript·面试·学习方法·数组去重
摇光935 小时前
promise
前端·面试·promise
Ai 编码助手5 小时前
Go语言 实现将中文转化为拼音
开发语言·后端·golang