精华内容
下载资源
问答
  • 详解Mark Word

    2021-08-11 22:51:52
    对于一个Java对象来说,对象头是极其重要的,对象头主要有对象标记和类型指针两部分组成,接下来需要重点看下对象标记,也就是Mark Word,下面是针对64位JVM(那也就是说还有32位的,考虑到现在基本都是64的了,32的...

    对于一个Java对象来说,对象头是极其重要的,对象头主要有对象标记和类型指针两部分组成,接下来需要重点看下对象标记,也就是Mark Word,下面是针对64位JVM(那也就是说还有32位的,考虑到现在基本都是64的了,32的直接pass掉)的Mark Word来说的,它的组成是这样的:

    在这里插入图片描述

    一个Java对象由对象头,实例数据和对齐填充组成,其中对象头是极其重要的,对象头是由对象标记Mark Word和类型指针组成,其中又以Mark Word最重要,对于Mark Word它占8个字节也就是64位!

    ok,以上内容都清楚吧!那咱就继续!

    接下来咱就以锁这个切入点去详细的看看这个Mark Word!

    我们都知道在并发情况下也就是要解决线程安全的话要加锁,其实这个加锁就是对对象加锁,那如何去判断或者说知道这个对象加锁了没有或者加的什么锁,这些个信息其实就保存在对象头中的Mark Word中,上面这张图就是关于一个Mark Word的具体结构,可以看出,一个Java对象其实在不同的状态下,它是不一样的,主要是有不同的锁,比如没有锁,轻量级锁,还有什么偏向锁,重量级锁等等,不同的锁,在Mark Word中就会有不同的状态标记。


    JOL的引入

    这就是一个空类,啥也没有,要怎么看这个对象标记Mark Word呢?这里我们需要加入一个依赖,也就是你的Java项目是个maven项目,可以引入pom依赖,然后添加以下依赖:

    <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.10</version>
    </dependency>
    

    这个依赖是干嘛的呢?这是一个代码工具叫做JOL,也就是Java Object Layout,主要就是用来分析java虚拟机中的对象布局的,也就是在java对象在虚拟机中的大小和分布,ok,接下来加入上述这个依赖之后我们就可以这样操作

    public static void main(String[] args) {
            System.out.println(VM.current().details());
        }
    

    这样我们就可以看出使用虚拟机的一些情况,看打印输出:
    在这里插入图片描述
    接着我们就来看下我们创建的那个空类是怎样的一个情况,可以这样操作:

    public static void main(String[] args) {
            MyClass myClass = new MyClass();
            //打印出相关的对象头信息
            System.out.println(ClassLayout.parseInstance(myClass).toPrintable());
        }
    

    得出以下内容:
    在这里插入图片描述
    OK了,相关信息打印出来了,记住这是myclass的相关内部信息,那这些都是啥呢?看上面的图,是不是有个“Object header” 就是对象头

    在这里插入图片描述
    这是啥?主要就是来看我们的一个空对象占多大空间,之前也说了,对象头包括对象标记8字节和类型指针8字节,我们先来看对象标记,是不是这个:

    在这里插入图片描述
    也就是起始位置是0,然后前进4个字节,此时的起始位置就变成了4,接着再前进4个字节,那此时起始位置就成了8,此时对象标记就占8个字节,这没啥问题,接着我们看,起始位置是8,然后继续前进4字节,到达12的时候,其实此时类型指针就结束了,也就是8到12,类型指针就占了4字节

    也就是此时这个对象就占据12个字节,可是不是说占据16个字节才对吗?我们知道java对象所占大小需要是8字节的整数倍,那为了满足这块,就又对齐填充了4字节,最终达到16字节 。

    为什么会这样?其实是因为这里发生了指针压缩,在说这个指针压缩之前,需要明白这样的一东西:

    在这里插入图片描述

    public class MyClass {
        char c='a';
        public static void main(String[] args) {
            MyClass myClass = new MyClass();
            //打印出相关的对象头信息
            System.out.println(ClassLayout.parseInstance(myClass).toPrintable());
        }
    }
    

    在空类中加入一个char类型,再看下结果:
    在这里插入图片描述
    接着我们再看一个:

    public class MyClass {
        char c='a';
        int s=100;
    }
    

    在这里插入图片描述

    为什么是6,一定要想明白了,记住8的倍数哦!对了,Instance size指的是一个对象有多大

    指针压缩

    接着我们再说之前指针压缩的问题,也就是类型指针是占8个字节的,可是实际上只占了4个字节,这就是指针压缩的问题,这里我们需要这么一个jvm参数,就是它:

    java -XX:+PrintCommandLineFlags -version
    

    也就是默认开启了指针压缩,所以我们的类型指针才变成了4字节。

    value值分析

    在这里插入图片描述

    展开全文
  • Mark Word 详解

    2021-10-17 15:07:57
    Mark Word 在32位 JVM 中: Mark Word 在64位 JVM 中: 锁标志位(lock) 区分锁状态,11时表示对象待GC回收状态, 只有最后2位锁标识(11)有效。 biased_lock 是否偏向锁,由于无锁和偏向锁的锁标识都是 01,没...

    Mark Word 在32位 JVM 中:
    在这里插入图片描述
    Mark Word 在64位 JVM 中:
    在这里插入图片描述

    • 锁标志位(lock)
      区分锁状态,11时表示对象待GC回收状态, 只有最后2位锁标识(11)有效。

    • biased_lock
      是否偏向锁,由于无锁和偏向锁的锁标识都是 01,没办法区分,这里引入一位的偏向锁标识位。

    • 分代年龄(age)
      表示对象被GC的次数,当该次数到达阈值的时候,对象就会转移到老年代。

    • 对象的hashcode(hash)
      运行期间调用System.identityHashCode()来计算,延迟计算,并把结果赋值到这里。当对象加锁后,计算的结果31位不够表示,在偏向锁,轻量锁,重量锁,hashcode会被转移到Monitor中。

    • 偏向锁的线程ID(JavaThread):
      偏向模式的时候,当某个线程持有对象的时候,对象这里就会被置为该线程的ID。 在后面的操作中,就无需再进行尝试获取锁的动作。

    • epoch
      偏向锁在CAS锁操作过程中,偏向性标识,表示对象更偏向哪个锁。

    • ptr_to_lock_record
      轻量级锁状态下,指向栈中锁记录的指针。当锁获取是无竞争的时,JVM使用原子操作而不是OS互斥。这种技术称为轻量级锁定。在轻量级锁定的情况下,JVM通过CAS操作在对象的标题字中设置指向锁记录的指针。

    • ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。如果两个不同的线程同时在同一个对象上竞争,则必须将轻量级锁定升级到Monitor以管理等待的线程。在重量级锁定的情况下,JVM在对象的ptr_to_heavyweight_monitor设置指向Monitor的指针。

    展开全文
  • 深入理解Java的对象头mark word

    千次阅读 2021-02-07 22:22:44
    上一篇博客我们编译了Linux源码来证明了Java中有偏向锁,但是我们从周志明大佬的《深入理解java虚拟机》的书中知道,我们可以通过分析Java对象头中MarkWord来查看是那种锁,下面是32位JVM的对象中的Mark Word图,...

    上一篇博客我们编译了Linux源码来证明了Java中有偏向锁,但是我们从周志明大佬的《深入理解java虚拟机》的书中知道,我们可以通过分析Java对象头中MarkWord来查看是那种锁,下面是32位JVM的对象中的Mark Word图,但是随着JDK的不断升级,JDK没有32位的版本,所以我们要研究64的JVM中对象的MarkWord。

    在这里插入图片描述
    当我在网上找了很多资料的后,发现都是32位JVM,无法满足我们对64位JVM的研究,于是我想到了JDK源码,看看其中有没有注释,于是我去编译好的JDK源码找找看,找到对应的源码的注释如下:

    在这里插入图片描述
    我们可以将上面的注释转成以下的表格

    |-----------------------------------------------------------------------------------------------------------------|
    |                                             Object Header(128bits)                                              |
    |-----------------------------------------------------------------------------------------------------------------|
    |                                   Mark Word(64bits)               |  Klass Word(64bits)    |      State         |
    |-----------------------------------------------------------------------------------------------------------------|
    | unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object |      Nomal         |
    |-----------------------------------------------------------------------------------------------------------------|
    | thread:54|      epoch:2       |unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object |      Biased        |
    |-----------------------------------------------------------------------------------------------------------------|
    |                     ptr_to_lock_record:62                 |lock:2 | OOP to metadata object | Lightweight Locked |
    |-----------------------------------------------------------------------------------------------------------------|
    |                    ptr_to_heavyweight_monitor:62          |lock:2 | OOP to metadata object | Heavyweight Locked |
    |-----------------------------------------------------------------------------------------------------------------|
    |                                                           |lock:2 | OOP to metadata object |    Marked for GC   |
    |-----------------------------------------------------------------------------------------------------------------|
    

    从上面的表格,我们可以看出Java的对象头在对象的不同的状态下会有不同的表现形式,主要有三种状态,无锁状态,加锁状态,GC标记状态。那么就可以理解Java当中的上锁其实可以理解给对象上锁。也就是改变对象头的状态,如果上锁成功则进入同步代码块。但是Java当中的锁又分为很多种,从上图可以看出大体分为偏向锁、轻量锁、重量锁三种锁状态。这三种锁的效率是完全不同、关于效率的分析会在下文分析。我们需要查看对象头,就需要用到借助JOL工具。

    首先我们在项目中引入JOL的依赖,具体如下图:

    <dependency>
         <groupId>org.openjdk.jol</groupId>
         <artifactId>jol-core</artifactId>
         <version>0.10</version>
    </dependency>
    

    然后创建A.java

    public class A{}
    
    • 然后创建JOLExample1.java
    import org.openjdk.jol.info.ClassLayout;
    import org.openjdk.jol.vm.VM;
    
    import static java.lang.System.out;
    
    public class JOLExample1 {
        static A a;
    
        public static void main(String[] args) {
            a = new A();
            //打印JVM的详细信息
            out.println(VM.current().details());
            //打印对应的对象头信息
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
    }
    

    运行的结果如下
    在这里插入图片描述

    不是说Klass是64bits(8个字节)但是这儿只有4个字节,是因为我们开启了指针压缩,我们可以关闭指针压缩看看,是不是8个字节。我们只需要使用以下的JVM运行参数

    -XX:-UseCompressedOops
    
    • 1

    在这里插入图片描述

    再次运行刚才的程序,可以看到我们Klass对象是64bits(16个字节),具体如下图

    在这里插入图片描述

    解决了上面的问题后,我们再回到原来开启指针压缩图上来

    在这里插入图片描述

    我们可以看到整个对象是16B(16个字节),其中对象头(object header)12B(12个字节),还有4B是对齐的字节(因为在64位虚拟机上对象的大小必须是8的倍数),由于这个对象里面没有任何字段,故而对象的实例数据为0B,那怎么让它不是0B呢?我们可以在A中添加一个boolean类型的数据,再看结果,修改A.java如下所示

    public class A {
        //占一个字节的boolean字段
        private boolean flag;
    }
    

    我们再次运行JOLExample.java,查看结果如下:

    在这里插入图片描述

    这个对象的大小还是没有改变一共16B,其中对象头(object header) 12B,boolean字段flag(对象的实例数据)占1B,剩下的3B就是对齐字节。由此我们可以认为一个对象的布局大体分为三个部分分别是对象头(Object header)、对象的实例数据、字节对齐。

    上面说是64位虚拟机上对象的大小必须是8的倍数,我们可以证明一下,再加一个int(4个字节)数据,我们再次修改A.java如下所示

    public class A {
        //占一个字节的boolean字段
        private boolean flag;
        //占四个字节的int字段
        private int a;
    }
    

    再次运行程序,如下所示,可以看到对象的大小是8的倍数

    在这里插入图片描述

    看完了对象的实例数据,我们就来到了今天的重头戏,Java的对象头(在开启JVM指针压缩的情况下是12B),那么这12B存储的是什么?我们可以看下
    OpenJDK的官网的解释

    首先引用openjdk文档当中对对象头的解释

    在这里插入图片描述

    上述引用中提到一个java对象头包含2个word,并且包含了堆对象的布局、类型、GC状态、同步状态和标识哈希码,具体怎么包含的呢?又是哪两个word呢?
    在这里插入图片描述

    Mark word为第一个word根据文档可以知道它里面包含了锁的信息、hashcode、gc信息等等,第二个word是什么呢?

    在这里插入图片描述

    klass word 为对象头的第二个word主要指向对象的元数据。

    在这里插入图片描述

    假设我们理解一个对象头主要由上图两个部分组成(数组对象除外,数组对象的对象还包含一个数组长度),由我们的推导出Mark word是8个字节,klass word(开启指针压缩的情况下是4个字节,不开启的时候是8个字节)。我们打印出来的对象头是12个字节,所以其中的8个字节是Mark word,剩下的4个字节是klass word,但是和锁相关的就是Mark word,那么接下来要重点分析Mark word里面信息。

    由最开始的64位的表格,我们可以得知在无锁的情况下Markword当中前56bit存的是对象的hashcode,我们来验证一下

    修改A.java 的代码如下

    public class A {
        //占一个字节的boolean字段
        private boolean flag;
    }
    

    新建一个JOLExample2.java具体代码如下

    import org.openjdk.jol.info.ClassLayout;
    
    import static java.lang.System.out;
    public class JOLExample2 {
    
        public static void main(String[] args) {
            A a = new A();
            //没有计算HashCode之前的对象头
            out.println("before hash");
            out.println(ClassLayout.parseInstance(a).toPrintable());
            
            //jvm计算HashCode
            out.println("jvm----------" + Integer.toHexString(a.hashCode()));
            
            //当计算完HashCode之后,我们可以查看对象头的信息变化
            out.println("after hash");
            out.println(ClassLayout.parseInstance(a).toPrintable());
            
        }
    }
    
    

    运行的结果如下:

    在这里插入图片描述

    可以看到我们在没有进行hashcode运算的时候,所有的值都是空的。当我们计算完了hashcode,对象头就是有了数据。因为是小端存储,所以你看的值是倒过来的。前25bit没有使用所以都是0,后面31bit存的hashcode,所以第一个字节中八位存储的分别就是分代年龄、偏向锁信息、对象状态,这8bit分别表示的信息如下图所示,这个图会随着对象的状态改变而改变,下图是无锁的状态下

    在这里插入图片描述

    关于对象状态一共分为五种状态,分别是无锁、偏向锁、轻量锁、重量锁、GC标记,但是2bit只能表示4种状态(00,01,10,11)JVM的做法将偏向锁和无锁的状态表示为同一个状态,然后根据图中偏向锁的标识再去标识是无锁还是偏向锁状态。写个代码分析一下,在写代码之前我们先记得无锁状态下的信息是00000001,写个偏向锁的例子如下所示

    新建一个JOLExample3.java,代码如下:

    import org.openjdk.jol.info.ClassLayout;
    
    import static java.lang.System.out;
    
    public class JOLExample3 {
    
        static A a;
    
        public static void main(String[] args) throws InterruptedException {
            a = new A();
            out.println("before lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
    
            sync();
    
            out.println("after lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
            
        }
    
        private static void sync() {
            synchronized (a) {
                out.println("我不知道要打印什么");
            }
        }
    }
    

    查看运行结果如下:

    在这里插入图片描述

    上面这个程序只有一个线程去调用sync方法,应该是偏向锁,但是你会发现输出的结果(第一个字节)依然是00000001和无锁的时候一模一样,其实这是因为虚拟机在启动的时候对于偏向锁有延迟,如果没有偏向锁的延迟的话,虚拟机在启动的时候,可能JVM某个线程调用你的线程,这样就有可能变成了轻量锁或者重量锁,所以要做偏向锁的延迟,那我们怎么看到打印的对象头是偏向锁呢?有两种方式:第一种是加锁之前先让线程睡几秒。第二种加上JVM的运行参数,关闭偏向锁的延迟,具体的命令如下:

    -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
    
    • 第一种方式:修改JOLExample3.java如下
    import org.openjdk.jol.info.ClassLayout;
    
    import static java.lang.System.out;
    
    public class JOLExample3 {
    
        static A a;
    
        public static void main(String[] args) throws InterruptedException {
            //切记延迟一定要放在对象创建之前,不然是无效的,因为在你对象创建之前,偏向锁的延迟的时间
            //没有给你睡过去,这时候,对象已经创建了,对象头的信息已经生成了。
            Thread.sleep(5000);
            a = new A();
            out.println("before lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
    
            sync();
    
            out.println("after lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
    
        }
    
        private static void sync() {
            synchronized (a) {
                out.println("lock ing");
                out.println(ClassLayout.parseInstance(a).toPrintable());
            }
        }
    }
    
    

    再次运行,查看结果如下:

    在这里插入图片描述

    可以发现已经变成了00000101,偏向锁,需要注意的after lock,退出同步后依然保持了偏向信息。

    第二种方式:利用jvm参数,首先我们先关闭睡眠5秒的,然后运行配置如下:

    在这里插入图片描述

    再次运行查看结果如下:

    在这里插入图片描述

    这时候大家会有疑问了,为什么在没有加锁之前是偏向锁,准确的说,应该是叫可偏向的状态,因为它后面没有存线程的ID,当lock ing的时候,后面存储的就是线程的ID(44969989)既然这儿存储是线程的ID,那么HashCode又存储到什么地方去了?是不是计算了HashCode就是不能偏向了?我们来验证一下,计算完HashCode,还是不是偏向锁了

    我们再次修改JOLExample3.java,具体代码如下:

    import org.openjdk.jol.info.ClassLayout;
    
    import static java.lang.System.out;
    
    public class JOLExample3 {
    
        static A a;
    
        public static void main(String[] args) throws InterruptedException {
            //切记延迟一定要放在对象创建之前,不然是无效的,因为在你对象创建之前,偏向锁的延迟的时间
            //没有给你睡过去,这时候,对象已经创建了,对象头的信息已经生成了。
            //Thread.sleep(5000);
            a = new A();
            out.println("before lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
            a.hashCode();
    
            sync();
    
            out.println("after lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
    
        }
    
        private static void sync() {
            synchronized (a) {
                out.println("lock ing");
                out.println(ClassLayout.parseInstance(a).toPrintable());
            }
        }
    }
    

    同时关闭JVM中偏向锁的延迟,运行的结果如下:
    在这里插入图片描述

    我们可以发现:在before lock的时候是可偏向的状态,lock ing的时候变成了轻量锁,after lock 的时候变成了无锁,所以我们得出对象计算了HashCode,就不是偏向锁了。

    看完了偏向锁的对象头,我们再来看看轻量锁的对象头,轻量级锁尝试在应用层面解决线程同步问题,而不触发操作系统的互斥操作,轻量级锁减少多线程进入互斥的几率,不能代替互斥。

    创建JOLExample4.java,代码如下:

    import org.openjdk.jol.info.ClassLayout;
    
    import static java.lang.System.out;
    
    public class JOLExample4 {
        static A a;
    
        public static void main(String[] args) {
            a = new A();
            out.println("before lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
    
            sync();
    
            out.println("after lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
    
        private static void sync() {
            synchronized (a) {
                out.println("lock ing");
                out.println(ClassLayout.parseInstance(a).toPrintable());
            }
        }
    }
    

    运行结果如下:

    在这里插入图片描述

    可以得出:before lock 的时候是 00000001 无锁的状态,lock ing 的时候是 01010000 轻量锁的状态,after lock 的时候是 00000001 无锁的状态。

    看完了轻量锁的对象头,我们再来看看重量锁的对象头,我们先创建一个JOLExample5.java具体代码如下:

    import org.openjdk.jol.info.ClassLayout;
    
    import static java.lang.System.out;
    
    public class JOLExample5 {
        static A a;
    
        public static void main(String[] args) throws InterruptedException {
            a = new A();
            out.println("before lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
            
            Thread t1 = new Thread(()->{
                synchronized (a) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    out.println("t1 release");
                }
            });
            t1.start();
            
            Thread.sleep(1000);
            out.println("t1 lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
            
            sync();
            out.println("after lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
    
            System.gc();
            out.println("after gc()");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
    
        private static void sync() {
            synchronized (a) {
                out.println("main lock ing");
                out.println(ClassLayout.parseInstance(a).toPrintable());
            }
        }
    }
    

    运行结果如下:

    在这里插入图片描述

    在加锁之前(before lock)是 00000001 无锁,这时候t1来加锁,因为只有他一个线程所以轻量锁(t1 lock ing 00010000)由于t1在run方法中睡眠了5秒,这时候主线程也来尝试加锁,这个时候就是两个线程竞争了,所以是重量锁(main lock ing 00101010
    在这里插入图片描述

    当结束的时候,还是重量锁(afteer lock 00101010),当执行一次gc操作过后发现变成了无锁但是年龄加了1(after gc() 00001001

    还有一点需要我们注意的就是:当调用wait方法会直接变成重量锁,我们来验证一下,创建JOLExample6.java,代码如下:

    import org.openjdk.jol.info.ClassLayout;
    
    import static java.lang.System.out;
    
    public class JOLExample6 {
        static A a;
    
        public static void main(String[] args) throws Exception {
    
            a = new A();
            out.println("before lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
    
            Thread t1 = new Thread(() -> {
                try {
                    synchronized (a) {
                        out.println("before wait");
                        out.println(ClassLayout.parseInstance(a).toPrintable());
                        a.wait();
                        out.println("after wait");
                        out.println(ClassLayout.parseInstance(a).toPrintable());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            });
            t1.start();
            Thread.sleep(5000);
            synchronized (a) {
                a.notifyAll();
            }
        }
    }
    

    运行结果如下:

    在这里插入图片描述

    可以看到在加锁之前是无锁的状态,执行wait方法之前是轻量锁,执行wait 方法之后,被唤醒的后是重量锁。

    既然synchronized关键字有这三种锁,我们简单的比较它们之间的性能(粗略的比较下),书写以下的代码

    public class A {
        int i;
        public synchronized void parse() {
            i++;
        }
    }
    
    //关闭偏向锁延迟‐XX:BiasedLockingStartupDelay=0
    public class JOLExample7 {
        public static void main(String[] args) throws Exception {
            A a = new A();
            long start = System.currentTimeMillis();
            //调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能
            //如果不出意外,结果灰常明显
            for (int i = 0; i < 1000000000L; i++) {
                a.parse();
            }
            long end = System.currentTimeMillis();
            System.out.println(String.format("%sms", end - start));
        }
    
    }
    

    先运行加上jvm参数关闭偏向锁延迟,就是偏向锁,然后运行的结果如下:

    在这里插入图片描述

    我们在开启偏向锁延迟就是轻量锁,然后运行结果如下:

    在这里插入图片描述

    最后我们在看重量锁,具体代码如下:

    public class A {
        int i;
        public synchronized void parse() {
            JOLExample8.countDownLatch.countDown();
            i++;
        }
    }
    
    
    import java.util.concurrent.CountDownLatch;
    
    public class JOLExample8 {
        static CountDownLatch countDownLatch = new CountDownLatch(1000000000);
    
        public static void main(String[] args) throws Exception {
            final A a = new A();
    
            long start = System.currentTimeMillis();
    
            //调用同步方法1000000000L 来计算1000000000L的++,对比各种锁的性能
            //如果不出意外,结果灰常明显
            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));
        }
    }
    

    重量级锁的执行结果如下:

    在这里插入图片描述

    最后总结的结果如下:

    偏向锁轻量锁重量锁
    2355ms23564ms31227ms

    最后我们再画个图总结下各种锁的对象头(只画出了最重要的部分,其他的省略)
    在这里插入图片描述

    展开全文
  • java对象头 MarkWord

    万次阅读 多人点赞 2019-06-05 20:41:15
    当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。 thread:持有偏向锁的线程ID。 epoch:偏向锁的时间戳。 ptr_to_lock_record:轻量级锁...

    原文链接:[https://blog.csdn.net/scdn_cp/article/details/86491792#comments]

    我们都知道,Java对象存储在堆(Heap)内存。那么一个Java对象到底包含什么呢?概括起来分为对象头、对象体和对齐字节。如下图所示:

    对象的几个部分的作用:

    1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;

    2.Klass Word是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;

    3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;

    4.对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型;

    5.对齐字是为了减少堆内存的碎片空间(不一定准确)。

    了解了对象的总体结构,接下来深入地了解对象头的三个部分。


    一、Mark Word(标记字)

    以上是Java对象处于5种不同状态时,Mark Word中64个位的表现形式,上面每一行代表对象处于某种状态时的样子。其中各部分的含义如下:

    lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。biased_lock和lock一起,表达的锁状态含义如下:

    biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。

    age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

    identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。

    thread:持有偏向锁的线程ID。

    epoch:偏向锁的时间戳。

    ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。

    ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。

      我们通常说的通过synchronized实现的同步锁,真实名称叫做重量级锁。但是重量级锁会造成线程排队(串行执行),且会使CPU在用户态和核心态之间频繁切换,所以代价高、效率低。为了提高效率,不会一开始就使用重量级锁,JVM在内部会根据需要,按如下步骤进行锁的升级:

            1.初期锁对象刚创建时,还没有任何线程来竞争,对象的Mark Word是下图的第一种情形,这偏向锁标识位是0,锁状态01,说明该对象处于无锁状态(无线程竞争它)。

            2.当有一个线程来竞争锁时,先用偏向锁,表示锁对象偏爱这个线程,这个线程要执行这个锁关联的任何代码,不需要再做任何检查和切换,这种竞争不激烈的情况下,效率非常高。这时Mark Word会记录自己偏爱的线程的ID,把该线程当做自己的熟人。如下图第二种情形。

            3.当有两个线程开始竞争这个锁对象,情况发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象并执行代码,锁对象的Mark Word就执行哪个线程的栈帧中的锁记录。如下图第三种情形。

            4.如果竞争的这个锁对象的线程更多,导致了更多的切换和等待,JVM会把该锁对象的锁升级为重量级锁,这个就叫做同步锁,这个锁对象Mark Word再次发生变化,会指向一个监视器对象,这个监视器对象用集合的形式,来登记和管理排队的线程。如下图第四种情形。

    二、Klass Word(类指针)
    这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。

    如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

    每个Class的属性指针(即静态变量)
    每个对象的属性指针(即对象变量)
    普通对象数组的每个元素指针
    当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
    三、数组长度

     如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。

    展开全文
  • 浅谈java对象结构 对象头 Markword 这篇文章主要介绍了浅谈java对象结构 对象头 Markword,具有很好的参考价值,希望对大家有所帮助 概述 对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,...
  • 并发基石-Markword与锁升级

    千次阅读 2019-03-28 12:30:12
    title: 并发基石-Markword与锁升级 synchronized synchronized关键字是java提供的互斥锁关键字,我们常说的互斥锁一般都是非自旋锁,即竞争不到锁的线程会进入阻塞状态知道被唤醒 今天我们来讲讲java中用来对...
  • JVM的markWord内容

    2021-07-07 20:27:58
    三、synchronized的锁升级过程中,markWord内容 锁状态:  当一个对象刚开始new出来时,该对象是无锁状态。此时偏向锁位为0,锁标志位01  如果有线程上锁:  指的就是把markword的线程ID改为自己线程ID的过程  ...
  • 当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。 thread :持有偏向锁的线程ID。 epoch :偏向锁的时间戳。 ptr_to_lock_record :轻...
  • MarkWord 翻译过来就是标记字,既然是标记,那么就肯定是用来记录一些信息,x86下4字节,x64下8字节,后面紧跟着klass pointer,目前jvm基本都是x64,我们都知道在x86下地址是4字节大小,在x64下地址是8字节大小,但...
  • 以上是 Java对象处于5种不同状态时,Mark Word中 64位的表现形式,上面每一行代表对象处于某种状态时的样子。其中各部分的含义如下:【1】lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息...
  • 第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度在32位和64位的虚拟机中分别为32个和64个Bits,官方称它为Mark Word,它是实现轻量级锁各偏向锁的...
  • 哈希函数(散列函数) 单向函数,反向运算无法完成 任意长度输入,固定长度输出 输入不变,... class指针 (属于哪个class类的) 锁信息(锁升级就再markword中做标志) hashCode(对象new出来是就算好的hashCode的值)
  • Java Mark Word和锁

    2020-10-29 10:40:39
    Mark Word简介 Mark Word在64位的虚拟机中也占64位,是每一个对象都有的一个值。这个区域记录了很多信息,并根据对象的锁状态能表达很多不同的意思。如下: 头2位为标志位:如上图所示,虚拟机根据这2位的判断该对象...
  • java对象结构 对象头 Markword

    万次阅读 2018-08-23 17:45:31
    对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ------------| --------- | | markword | 32bit | 64bit | | 类型...
  • 无锁 无锁:MarkWord标志位01,没有线程执行同步方法/代码块时的状态 偏向锁 我们认为很多时候遇不到多线程高并发的情况,我们就偏向于第一个进来的线程。具体的实现过程: 当一个线程访问同步块并获取锁时,会在...
  • JAVA 对象头MarkWord

    2020-12-11 15:02:54
    JAVA 对象头MarkWord 最近在并发编程的学习中,接触到了JAVA对象头中的MarkWord. 在此做下笔记,也和小伙伴们分享一下,共同进步! MarkWord介绍 MarkWord是Java对象存储在内存中的一部分信息. 在32位系统中使用32位存储...
  • 文章目录Monitor监视器java对象头Monitor Monitor监视器 ...MarkWord里默认数据是存储对象的HashCode等信息,但是在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。 重量级锁也就是通常说
  • JDK8 markword实现表 64位最新版 无锁 - 偏向锁 - 轻量级锁 (自旋锁,自适应自旋)- 重量级锁 synchronized优化的过程和markword息息相关 用markword中最低的三位代表锁状态 其中1位是偏向锁位,两位是普通锁位 ...
  • -XX:-UseCompressedOops 开启:-XX:-UseCompressedOops 对象头具体包括什么 32位 markword 64bit (8byte) : 3位锁标志, 4位分代年龄(GC标记,被回收了多少次), 25 位hashcode(没有重写过hashcode方法,默认使用的...
  • Java openjdk 提供jol 工具,可以查看class的头信息。 下载 jol 工具包:下载地址 选择一个版本,进去后下载 jol-cli-.-full.jar 一定要下载full 的jar 导入包。 打印: System.out.println(ClassLayout....
  • markword用于标记锁的位数

    千次阅读 2020-01-11 20:35:31
    markword共64位(8字节) 1位用于记录是否时偏向锁; 2位用于记录锁标志位。
  • 深入理解 java 对象头的 Mark Word--------源码分析 面试题:一个空对象的创建占几个字节
  • java 对象头 我们都知道,Java对象存储在堆(Heap)内存...1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode; 2.Klass Word是一个指向方法区中Class信.
  • 包括: ...可以查看出markword 的值会改变 根据下面的一张图,回答问题 我们能不能将GC年龄调高一点,变成32? 不能,因为分代年龄存储的字节只有4位,只能存储最多16的数据,不可能提高到32 ...
  • 文章目录1.java对象的内存布局1.1 导入jol1.2 空对象的内存布局1.3 数组的对象布局1.4 synchronized之后的对象布局2.MarkWord3.synchronized的锁升级简介3.synchronized的字节码 在前面聊过了如何使用synchronized,...
  • 再说对象头的MarkWord⑦. 聊聊Object obj = new Object() ①. 对象在堆内存中的存储布局 ①. 对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。 ②. 对象头分为对象标记(markOop)和类元信息...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,855
精华内容 18,742
关键字:

MARKWORD