学习——java对象解析和锁升级

Posted 要千

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习——java对象解析和锁升级相关的知识,希望对你有一定的参考价值。

申请对象

是使用new创建一个对象时,字节码指令如下

package jvm.study;
CODE:
public class Test 
	public static void main(String[] args) 
		Test t = new Test();
	

Main 方法中BYTECODE
    0  new jvm.study.Test [1]
    3  dup
    4  invokespecial jvm.study.Test() [16]
    7  astore_1 [t]
    8  return

1,,当调用new指令后,jvm会在堆内存中申请一块内存(如果有属性,初始化为零值),同时在操作数栈中push一个引用指向新申请内存
2,执行dup指令,复制new指令时创建的引用,push到操作数栈,此时,栈中有两个指向新申请的内存
3,invokespecial指令,通过常量池找到需要jvm.study.Test()构造方法,同时弹出栈顶的元素找到当前构造方法所属的Class对象,执行对象的构造(如果有属性初始值的,在此更新零值为初始值)
4,astore_1指令,把引用栈顶元素赋值给本地变量t,同时弹出栈顶,一个java对象构造完成

:JVM优化可能会将指令3和指令4重排序,这里就会出现半初始化对象被其他线程使用,导致程序出问题!!!
某大厂的一道面试题DCL(double check lock)单例,需要使用volatile关键字标记吗?

对象头

java对象通常是有对象头,对象体,填充区三部分。对象长度必须是8BYTE的整数倍,所以长度不够时需要填充区去填充,这部分空间就被浪费了
对象头
对象头通常是不定长的,它通常有mark wordkclass point两部分组成,如果对象时数组时,它还需要额外的空间存储数组的长度(64位JVM下占64位,当开启压缩指针-XX:+UseCompressedOops时,32位)
对象头的布局

|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |       
|--------------------------------------------------------------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |    GC
|--------------------------------------------------------------------------------------------------------------|

lock: 锁状态标记位,该标记的值不同,整个mark word表示的含义不同。
biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

age:Java GC标记位对象年龄(4bit,所以设置最大最大gc年龄时最大值不能超过15)。
identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。
thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。
epoch:偏向时间戳
ptr_to_lock_record:指向栈中锁记录的指针
Lock record:The lock record holds the original value of the object’s mark word and also contains metadata necessary to identify which object is locked
当字节码解释器执行monitorenter字节码轻度锁住一个对象时,就会在获取锁的线程的栈上显式或者隐式分配一个lock record。
lock record 包含两部分数据,分别是

  1. 对象被锁前的mark word
  2. 指向被锁队对象的引用
//Open jdk 实现
// A BasicObjectLock associates a specific Java object with a BasicLock.
// It is currently embedded in an interpreter frame.
class BasicObjectLock VALUE_OBJ_CLASS_SPEC 
 private:
  BasicLock _lock;                        // the lock, must be double word aligned
  oop       _obj;                         // object holds the lock;
;
class BasicLock VALUE_OBJ_CLASS_SPEC 
 private:
  volatile markOop _displaced_header;
;

lock record 作用:

  • 持有displaced word和锁住对象的元数据;
  • 解释器使用lock record来检测非法的锁状态;
  • 隐式地充当锁重入机制的计数器

ptr_to_heavyweight_monitor:指向线程Monitor的指针。

对象锁的升级

public static void main(String[] args) throws Exception 
        A o = new A();
        System.out.println(o.toString());
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    

对象未加锁时布局如下:

 public static void main(String[] args) throws Exception 

        A o = new A();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        synchronized (o) 
            System.out.println("App.main syn block");
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        
        System.out.println("App.main. realease lock..");
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

    

public static void main(String[] args) throws Exception 

        A o = new A();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        new Thread(() -> 
            synchronized (o) 
                try 
                    Thread.sleep(2000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println("App.main syn block");
                System.out.println(ClassLayout.parseInstance(o).toPrintable());
            

        ).start();
        new Thread(() -> 
            synchronized (o) 
                try 
                    Thread.sleep(2000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println(ClassLayout.parseInstance(o).toPrintable());
            
        ).start();

    


测试偏向锁,可以使用下列代码

public static void main(String[] args) throws InterruptedException 
    Thread.sleep(5000);
    A a = new A();
    System.out.println(ClassLayout.parseInstance(a).toPrintable());

以上是关于学习——java对象解析和锁升级的主要内容,如果未能解决你的问题,请参考以下文章

JAVA对象分析之偏向锁轻量级锁重量级锁升级过程

深度分析:锁升级过程和锁状态,看完这篇你就懂了!

Synchronized和锁升级

java synchronized中锁的升级过程

java synchronized中锁的升级过程

java学习笔记——java中对象的创建,初始化,引用的解析