学习——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 word和kclass 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 包含两部分数据,分别是
- 对象被锁前的mark word
- 指向被锁队对象的引用
//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对象解析和锁升级的主要内容,如果未能解决你的问题,请参考以下文章