1. 对象结构
1.1 对象结构概览
1. 对象头:Instance Header
Java对象最复杂的一部分,采用C++定义了头的协议格式,存储了Java对象hash、GC年龄、锁标记、class指针、数组长度等信息,稍后做出详细解说。
2. 实例数据:Instance Data
这部分数据才是真正具有业务意义的数据,实际上就是当前对象中的实例字段。
在VM中,对象的字段是由基本数据类型和引用类型组成的。
对象的字段们有如下规则:
1:除了对象整体需要按8字节对齐外,每个成员变量都尽量使本身的大小在内存中尽量对齐。比如 int 按 4 位对齐,long 按 8 位对齐。
2:类属性按照如下优先级进行排列:长整型long和双精度类型double;整型int和浮点型float;字符char和短整型short;字节类型byte和布尔类型boolean;最后是引用类型。这些属性都按照各自的单位对齐。
3:优先按照规则一和二处理父类中的成员,接着才是子类的成员。
4:当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话,就必须扩展到4个字节的基本单位。
5:如果子类第一个成员是一个双精度或者长整型,并且父类并没有用完8个字节,JVM会破坏规则2,按照整形(int),短整型(short),字节型(byte),引用类型(reference)的顺序,向未填满的空间填充。
3. 对齐填充(可能存在):
在hotSpot虚拟机中,默认的对齐位数是8,与CPU架构无关。
1.2 对象的占用空间
1.2.1 理论分析
对象占用空间 =
对象头(
Mark Word (32位JVM,4字节)(64位JVM,8字节)
+ Klass指针 (32位JVM,4字节)(64位JVM,8字节)(64位JVM && 默认开启压缩-XX:+UseCompressedOops,4字节)
+ Array length (固定为4字节,因为数组最大长度是int最大值)
)
(Klass指针的大小根据是否开启压缩而定-XX:+UseCompressedOops)
+ 实例数据(
+ 对象内部的基本类型属性:根据其大小而定比如 int=4字节 。byte 1;boolean 1;char 2;short 2;int 4;float 4;long 8;double 8
+ 对象内部的引用类型属性:只需要占用其 Klass指针部分即可( 即4字节或者8字节)
)
+ 对齐填充(
补齐为8字节的倍数
)
一个含有两个元素的int数组的占用空间为:
8(Mark Word)+
4(Klass指针且开启压缩)+
4(Array length )+
4*2(两个int)
=24字节
一个字符串对象占用内存空间为:
Mark Word (32位JVM,4字节)(64位JVM,8字节)+
Klass指针 (32位JVM,4字节)(64位JVM,8字节)(64位JVM && 默认开启压缩-XX:+UseCompressedOops,4字节)+
1个 int 属性(4字节)+
1个 long 属性(8字节)+
char 数组属性(Mark Word 8字节 + Klass指针4字节 + Array length 固定为4字节 + n个char占用2n字节)+
对齐填充
= 40字节+2n字节+对齐填充。
注意,说明一个问题,数组在作为属性时,占用的空间并不只是Klass指针4字节,而是数组的占用的全部内存?
1.2.2 打印显示对象占用空间方法1:
使用Unsafe:
java中的sun.misc.Unsafe类,有一个objectFieldOffset(Field f)方法,表示获取指定字段在所在实例中的起始地址偏移量,如此可以计算出指定的对象中每个字段的偏移量,
值为最大的那个就是最后一个字段的首地址,加上该字段的实际大小,就能知道该对象整体的大小。
1 class test{
2 boolean age;
3 int name;
4 boolean married;
5 }
1 static sun.misc.Unsafe U = null;
2
3 static Field theUnsafeInstance = null;
4
5 static {
6 try {
7 theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
8 theUnsafeInstance.setAccessible(true);
9 U = (Unsafe) theUnsafeInstance.get(Unsafe.class);
10 } catch (NoSuchFieldException e) {
11 e.printStackTrace();
12 } catch (IllegalAccessException e) {
13 e.printStackTrace();
14 }
15 }
1 public static void main(String[] args) {
2 try {
3 Field field1 = test.class.getDeclaredField("age");
4 System.out.println(U.objectFieldOffset(field1));
5 Field field2 = test.class.getDeclaredField("name");
6 System.out.println(U.objectFieldOffset(field2));
7 Field field3 = test.class.getDeclaredField("married");
8 System.out.println(U.objectFieldOffset(field3));
9 Field field4 = test.class.getDeclaredField("hh");
10 System.out.println(U.objectFieldOffset(field4));
11
12 } catch (NoSuchFieldException e) {
13 e.printStackTrace();
14 }
15
16 }
1 16
2 12
3 17
4 java.lang.NoSuchFieldException: hh
5 at java.lang.Class.getDeclaredField(Class.java:2070)
6 at test.main(test.java:46)
可以看到,通过Unsafe.objectFieldOffset(fieldName)方法计算看到:
test类的age/name/married三个字段的起始偏移量分别是16、12、17
而又因为married是boolean类型(见1.3.0),这个boolean字段排在在实例数据的最后,所以married字段的结尾偏移量为17+1。
所以可以算出每个test类对象占用空间大小 = 18 + 6= 24字节.(6字节是padding)
1.2.3 打印对象占用空间方法2:
见下文《1.4 打印各种锁对象》。
1.3 对象、对象头详解
1.3.0 OOP-Klass模型
JVM中把我们上层可见的Java对象在底层实际上表示为两部分:oop部分 + Klass部分,
Oop保存在堆上,专注于表示对象的信息,就是通常意义上的堆中对象。
(对象头、实例数据、对其填充)
Klass保存在方法区,专注于表示类的信息。(所以保存在方法区)
猜测方法区的内部结构以类为单位的一坨一坨的块。这么看应该就是Klass了。所以Klass保存的信息 = 方法区保存的信息。
(类型信息、运行时常量池、字段信息、方法信息等)
1.3.1 对象之OOP
当我们基于某个Java类用new创建一个新对象时,JVM会为其在堆上创建一个相应的oopDesc类的子类(instanceOopDesc)实例,用于存放该对象的(对象头、实例数据、对其填充)。
注意和Klass的定义区分,Klass是类加载时生成、用于存放该类的东西即metadata。
而在堆上创建的这个instanceOopDesc所对应的地址会被用来创建一个引用,赋给当前线程运行时栈上的一个变量。
1.3.1.1 oopDesc类:
所在位置:oop.hpp文件。
是所有Oop的共同基类,
这个类的内部结构就是通常意义上的堆中对象的结构:对象头、 实例数据、对齐填充。
1.3.1.2 instanceOopDesc类:
所在位置:instanceOop.hpp文件。
继承于oopDesc类。
理解为Java对象(堆中部分)的C++实现。
这个类的内部结构,比其父类多了header_size等三个看不懂干嘛的属性。
1.3.1.3 举个栗子:arrayOopDesc类
所在位置:arrayOop.hpp文件。
继承于oopDesc类,
理解为数组对象(堆中部分)的C++实现的基类,即所有数组对象的oop部分都继承于他。
1.3.2 对象之Klass
一个Java类在被JVM加载时,JVM会为其在方法区开辟一个相应的Klass类的子类(instanceKlass)空间,用于存放该类的(类型信息、运行时常量池、字段信息、方法信息等),
(猜测)这些信息就叫类的元信息metadata。
注意和OOP的定义区分,OOP是创建对象时生成、用于存放该对象的东西。
1.3.2.1 Klass类:
所在位置:Klass.hpp文件。
是所有Klass的共同基类,
这个类的内部结构没看懂。
1.3.2.2 InstanceKlass类:
所在位置:instanceKlass.hpp文件。
继承于Klass类。
理解为Java对象(方法区部分)的C++实现。
这个类的内部结构,比其父类(Klass类)多了KlassFieldClosure、OopMapBlock等。
1.3.2.3 举个栗子:arrayKlass类
所在位置:arrayKlass.hpp文件。
继承于Klass类。
理解为数组对象(方法区部分)的C++实现的基类,即所有数组对象的Klass部分都继承于他。
1.3.3 对象之Oop的代码实现(即oopDesc类的C++代码结构,即oop.hpp文件)
看一下oopDesc类的C++代码结构:(原则上应该看instanceOopDesc类源码,但是他绝大多数都是继承于oopDesc,自己只有三个看不懂干嘛的属性)
可以看到它含有以下属性:
1 //hotspot/src/share/vm/oops/oop.hpp
2 class oopDesc {
3 friend class VMStructs;
4 private:
5 volatile markOop _mark; // 属性之:对象头的markword
6 union _metadata { // 属性之:对象头的klass
7 Klass* _klass;
8 narrowKlass _compressed_klass;
9 } _metadata;
10 public:
11 jbyte* byte_field_addr(int offset) const; // 属性之:实例数据之:该对象可能包含的八种基本类型指向的地址、引用类型指向的地址
12 jchar* char_field_addr(int offset) const;
13 jboolean* bool_field_addr(int offset) const;
14 jint* int_field_addr(int offset) const;
15 jshort* short_field_addr(int offset) const;
16 jlong* long_field_addr(int offset) const;
17 jfloat* float_field_addr(int offset) const;
18 jdouble* double_field_addr(int offset) const;
19 Metadata** metadata_field_addr(int offset) const;
20 public:
21 void* field_base(int offset) const; //猜测是对齐填充
22 }
1.3.3.1. 对象之Oop的对象头的markword的代码实现:
一个markOopDesc类型的属性,属性名字叫做_mark。
1.3.3.2. 对象之Oop的对象头的klass的代码实现:
一个指向方法区Klass实例(即指向该类的metadata)的指针 或者 一个narrowKlass类型的属性(即指向该类的metadata)
,只能是两者之一,属性名字叫做_metadata 。
(当JVM开启了 -XX:UseCompressedOops选项时,就表示启用指针压缩,则使用narrowKlass类型封装“指向Klass实例的指针”,否则直接使用“指向Klass实例的指针”)
1.3.3.3. 对象之Oop的对象头的arraylength的代码实现:
代码中没找到。
1.3.3.4. 对象之Oop的实例数据的代码实现:
猜测保存的是各种类型实例的地址。代码中没找到。
1.3.3.5. 对象之Oop的对齐填充的代码实现:
代码中没找到。
1.3.4 对象之Klass的代码实现(即InstanceKlass类的C++代码结构,即InstanceKlass.hpp文件)
这个类的内部结构,比其父类(Klass类,Klass.hpp)多了KlassFieldClosure字段、OopMapBlock字段等。
1.3.4.1 对象之Klass的KlassFieldClosure字段
不知道干嘛的
1.3.4.2 对象之Klass的OopMapBlock字段
1 class OopMapBlock VALUE_OBJ_CLASS_SPEC {
2 private:
3 int _offset;
4 uint _count;
5 };
这个属性很重要。
由于GC收集时要扫描存活的对象(不考虑对象的非指针域、只扫描对象的指针域),那么这个时候就会用到每个类(每个InstanceKlass)的OopMapBlock结构(字段)。
每个InstanceKlass可能有多个OopMapBlock,子类一个、父类一个。
OopMapBlock里面记录着这个实例中的子对象数量、子对象们的地址。具体如何数据的呢:
OopMapBlock有两个属性:_offset、_count。
offset描述第一个所引用的oop相对于当前oop地址的偏移量,
count表示包含的oop的个数,所以寻找oop时从offset向后寻找count个oop即可。
前面1.1.2小节说过:类属性按照如下优先级进行排列:长整型long和双精度类型double;整型int和浮点型float;字符char和短整型short;字节类型byte和布尔类型boolean;最后是引用类型。这些属性都按照各自的单位对齐。
说明堆中对象的引用类型属性地址都是挨着的。
关于OopMapBlock我以前有个疑问:
我的想法是:因为InstanceKlass存在于方法区中,而OopMapBlock在InstanceKlass中,所以OopMapBlock也存在于方法区。
那么OopMapBlock里面存的就应该是:这个Klass所引用的别的Klass的信息。那为什么GC时遍历OopMapBlock就可以标记存活对象了呢?因为按道理说OopMapBlock存的是Klass信息,而不是对象信息。
回答:
Class信息只是提供了对象创建的模板, 定义了内存的布局. 简单来说, 就是给定一个特定class类型的对象, 我们可以知道x偏移处, 这个对象的类型是什么.
而对于用这个模板创建的具体对象来说, 这个偏移处的引用可能为null, 也可能是某个具体的对象. GC在扫描这个对象时, 需要根据这个对象的内存布局, 找到里面对象引用成员的具体值.
这个信息在Class的模板中是可以找到的, 但是Class模板中的信息太多了, 比如某个Class有100个字段, 但只有一个是对象引用, 其他全部是原始的int这种类型, 根据Class信息来扫描就会很效率很低.
这个时候, OopMapBlock只要告诉GC, 这个对象内存最后的8个字节是对象引用, 那么GC只要扫描最后这个内存去找对象指针就行了.
1.3.5 对象头之_klass
即oopDesc类的_metadata 属性,是一个指向方法区Klass实例的指针。
作用是,指向方法区中本对象的instanceKlass。
这个概念很重要,它连接了同一个对象的堆中部分(oop)和方法区部分(Klass)。
(32位JVM,对象头之_klass所占4字节)
(64位JVM,不开启指针压缩,对象头之_klass所占8字节)
(64位JVM ,默认开启压缩-XX:+UseCompressedOops,对象头之_klass所占4字节)
1.3.6 对象头之markword
即oopDesc类的_mark属性。类型为markOop类。
作用是,保存一些GC分代年龄、锁状态标记、哈希码、epoch、是否可偏向等信息。
(32位JVM,对象头之markword所占4字节)
(64位JVM,对象头之markword所占8字节)
1.3.7 对象头之各个子部分实现
看一下markOop.hhp文件源码,目的是想要先了解一下markword内部,从而了解整个对象头:
下面的代码为markOop.hhp的注释部分,表示的是markword各部分占用的空间。
1 // Bit-format of an object header (most significant first, big endian layout below):
2 //
3 // 32 bits:
4 // --------
5 // hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
6 // JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
7 // size:32 ------------------------------------------>| (CMS free block)
8 // PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
9 //
10 // 64 bits:
11 // --------
12 // unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
13 // JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
14 // PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
15 // size:64 ----------------------------------------------------->| (CMS free block)
16 //
17 // unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
18 // JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
19 // narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
20 // unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
1.3.7.1 对象头之各个子部分实现(64位环境 + 未开启指针压缩)
虽然上面的源码没看懂,但是有人总结了对象头的各部分结构占用的空间:
64位环境未开启指针压缩的场景:
对象无锁状态时:
1. markword = 64位 = 未使用空间25位 + 对象identity_hashcode31位 + 未使用空间1位 + 分代年龄4位 + 是否偏向锁1位 (0)+ 锁标志位2位 (01)
2. _klass = 64位 = 指向该对象所属类的存放法在方法区中的元信息metadata。
3. Array length = 32位 = 用于储存数组长度(所以数组最大长度为int最大值)
4. 合计空间 = 128位(16字节) 或者 160位(20字节)
对象为偏向锁时:
1. markword = 64位 = thread54位 + epoch2位 + 未使用空间1位 + 分代年龄4位 + 是否偏向锁1位 (1)+ 锁标志位2位 (01)
2. _klass = 64位 = 指向该对象所属类的存放法在方法区中的元信息metadata。
3. Array length = 32位 = 用于储存数组长度(所以数组最大长度为int最大值)
4. 合计空间 = 128位(16字节) 或者 160位(20字节)
对象为轻量锁时:
1. markword = 64位 = 指向栈中锁记录LockRecord的指针62位 + 锁标志位2位 (00)
2. _klass = 64位 = 指向该对象所属类的存放法在方法区中的元信息metadata。
3. Array length = 32位 = 用于储存数组长度(所以数组最大长度为int最大值)
4. 合计空间 = 128位(16字节) 或者 160位(20字节)
对象为重量锁时:
1. markword = 64位 = 指向指向管程Monitor的指针62位 + 锁标志位2位 (10)
2. _klass = 64位 = 指向该对象所属类的存放法在方法区中的元信息metadata。
3. Array length = 32位 = 用于储存数组长度(所以数组最大长度为int最大值)
4. 合计空间 = 128位(16字节) 或者 160位(20字节)
对象在GC执行标记算法时被插入到空闲链表时:
1. 空位62位 + 锁标志位2位 (11)
2. _klass = 64位 = 指向该对象所属类的存放法在方法区中的元信息metadata。
3. Array length = 32位 = 用于储存数组长度(所以数组最大长度为int最大值)
4. 合计空间 = 128位(16字节) 或者 160位(20字节)
几点说明:
identity_hashcode:对象的hash码,hash代表的并不一定是对象的(虚拟)内存地址,但依赖于内存地址,具体取决于运行时库和JVM的具体实现,底层由C++实现,实现细节参考OpenJDK源码。但可以简单的理解为对象的内存地址的整型值。
thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。
epoch:偏向时间戳。
1.3.7.2 对象头之各个子部分实现(64位环境 + 开启指针压缩)
应该就是把_klass改为32位即可。
不想研究了。
1.3.7.3 对象头之各个子部分实现(32位环境)
略。
1.4 打印各种锁对象
1.4.1 前置条件:
openjdk.jol包的Gradle依赖:compile 'org.openjdk.jol:jol-core:0.9'
1 public class A {
2 boolean flag = false;
3 String hy = "hhh";
4 }
1.4.2 打印:无锁对象:
1 public class PrintObject {
2 public static void main(String[] args) {
3 A a = new A();
4 System.out.println(ClassLayout.parseInstance(a).toPrintable());
5 }
6 }
7
8 BiasedLock.A object internals:
9 OFFSET SIZE TYPE DESCRIPTION VALUE
10 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
11 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
12 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
13 12 1 boolean A.flag false
14 13 3 (alignment/padding gap)
15 16 4 java.lang.String A.hy (object)
16 20 4 (loss due to the next object alignment)
17 Instance size: 24 bytes
18 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
第一行+第二行:对象头的markword(一共64位,8字节)
001结尾(因为锁标志位是01)(原则上应该还有hashcode,但是不知道为啥没有)
第三行:对象a的对象头的_klass(压缩,所以是32位,4字节)
第四行:对象a的boolean类型字段(1字节)
第五行:不知道为啥补了3字节,猜测是为了补齐boolean1字节——>4字节(3字节)
第六行:对象a的String类型字段(即String对象的_klass,4字节)
第七行:为了凑齐8字节的倍数,所以补了4字节(4字节)
合计:24字节
1.4.3 打印:匿名偏向锁对象:
(需要上来就把线程sleep一会儿,sleep时长为“偏向锁延迟加载时间”,目的是启动偏向锁,即当前程序所有对象的初始状态都是匿名偏向锁对象)
1 public class PrintObject {
2 public static void main(String[] args) throws InterruptedException{
3 Thread.sleep(5000);
4 A a = new A();
5 System.out.println(ClassLayout.parseInstance(a).toPrintable());
6 }
7 }
8
9 BiasedLock.A object internals:
10 OFFSET SIZE TYPE DESCRIPTION VALUE
11 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
12 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
13 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
14 12 1 boolean A.flag false
15 13 3 (alignment/padding gap)
16 16 4 java.lang.String A.hy (object)
17 20 4 (loss due to the next object alignment)
18 Instance size: 24 bytes
19 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
第一行+第二行:对象头的markword(一共64位,8字节)
101结尾(因为锁标志位是01 + 是否为偏向锁为1)
不偏向任何线程的偏向锁对象的对象头的markword特征是:第一个字节以101结尾 + 线程ID为0
第三行:对象a的对象头的_klass(压缩,所以是32位,4字节)
第四行:对象a的boolean类型字段(1字节)
第五行:不知道为啥补了3字节,猜测是为了补齐boolean1字节——>4字节(3字节)
第六行:对象a的String类型字段(即String对象的_klass,4字节)
第七行:为了凑齐8字节的倍数,所以补了4字节(4字节)
合计:24字节
问题:
默认“偏向锁延迟加载时间”是多少,以及如何修改?
回答:
默认时间为5s,猜测这个5s的意思是,程序启动5s之后创建的所有对象头的初始状态都是不偏向任何线程的偏向锁状态。
可以手动修改时间:-XX:BiasedLockingStartupDelay=0,表示修改为0s,即程序启动时就启动偏向锁。
也可以手动禁止偏向锁:-XX:-UseBiasedLocking=false 关闭偏向锁,默认进入轻量级锁
1.4.4 打印:偏向线程的偏向锁对象:
(在匿名偏向锁的基础上,再加个synchronized )
1 public class PrintObject {
2 public static void main(String[] args) throws InterruptedException{
3 Thread.sleep(5000);
4 A a = new A();
5 synchronized (a) {
6 System.out.println(ClassLayout.parseInstance(a).toPrintable());
7 }
8 System.out.println(ClassLayout.parseInstance(a).toPrintable());
9 }
10 }
11 ===================================================================================================================================================================
12 BiasedLock.A object internals:
13 OFFSET SIZE TYPE DESCRIPTION VALUE
14 0 4 (object header) 05 28 27 03 (00000101 00101000 00100111 00000011) (52897797)
15 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
17 12 1 boolean A.flag false
18 13 3 (alignment/padding gap)
19 16 4 java.lang.String A.hy (object)
20 20 4 (loss due to the next object alignment)
21 Instance size: 24 bytes
22 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
23
24 BiasedLock.A object internals:
25 OFFSET SIZE TYPE DESCRIPTION VALUE
26 0 4 (object header) 05 28 27 03 (00000101 00101000 00100111 00000011) (52897797)
27 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
28 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
29 12 1 boolean A.flag false
30 13 3 (alignment/padding gap)
31 16 4 java.lang.String A.hy (object)
32 20 4 (loss due to the next object alignment)
33 Instance size: 24 bytes
34 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
(1)第一坨结果:
第一行+第二行:对象头的markword,(一共64位,8字节)
101结尾(因为锁标志位是01 + 是否为偏向锁为1)
偏向了线程的偏向锁对象的对象头的markword特征是:第一个字节以101结尾 + 有线程ID
第三行:对象a的对象头的_klass(压缩,所以是32位,4字节)
第四行:对象a的boolean类型字段(1字节)
第五行:不知道为啥补了3字节,猜测是为了补齐boolean1字节——>4字节(3字节)
第六行:对象a的String类型字段(即String对象的_klass,4字节)
第七行:为了凑齐8字节的倍数,所以补了4字节(4字节)
合计:24字节
(2)第二坨结果:
因为偏向锁退出同步块以后,线程ID仍然不改变。
所以第二坨和第一坨相同。
1.4.5 打印:从无锁直接升级为轻量锁对象:
(跳过匿名偏向锁,趁着宏程序还未开启偏向锁时,就加上synchronized )
1 public class PrintObject {
2 public static void main(String[] args) throws InterruptedException{
3 // Thread.sleep(5000);
4 A a = new A();
5 synchronized (a) {
6 System.out.println(ClassLayout.parseInstance(a).toPrintable());
7 }
8 System.out.println(ClassLayout.parseInstance(a).toPrintable());
9 }
10 }
11 ====================================================================================================================================================================
12 BiasedLock.A object internals:
13 OFFSET SIZE TYPE DESCRIPTION VALUE
14 0 4 (object header) 88 f2 f1 02 (10001000 11110010 11110001 00000010) (49410696)
15 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
17 12 1 boolean A.flag false
18 13 3 (alignment/padding gap)
19 16 4 java.lang.String A.hy (object)
20 20 4 (loss due to the next object alignment)
21 Instance size: 24 bytes
22 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
23
24 BiasedLock.A object internals:
25 OFFSET SIZE TYPE DESCRIPTION VALUE
26 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
27 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
28 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
29 12 1 boolean A.flag false
30 13 3 (alignment/padding gap)
31 16 4 java.lang.String A.hy (object)
32 20 4 (loss due to the next object alignment)
33 Instance size: 24 bytes
34 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
(1)第一坨结果:
第一行+第二行:对象头的markword,(一共64位,8字节)
00结尾 + 指向栈中锁记录LockRecord
第三行:对象a的对象头的_klass(压缩,所以是32位,4字节)
第四行:对象a的boolean类型字段(1字节)
第五行:不知道为啥补了3字节,猜测是为了补齐boolean1字节——>4字节(3字节)
第六行:对象a的String类型字段(即String对象的_klass,4字节)
第七行:为了凑齐8字节的倍数,所以补了4字节(4字节)
合计:24字节
(2)第二坨结果:
因为轻量锁退出同步块以后,锁退化为无锁状态。
所以第二坨和无锁对象相同。
1.4.6 打印:从偏向锁升级为轻量锁对象:
(匿名偏向锁 + 两个以上线程竞争 + 利用join实现不同时竞争一个对象)
1 public class PrintObject {
2
3 public static void main(String[] args) throws InterruptedException{
4 Thread.sleep(5000);
5 A a = new A();
6
7 Thread t1 = new Thread(() -> {
8 synchronized (a){
9 System.out.println("thread1 locking===========================");
10 System.out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向锁
11 }
12
13 });
14 t1.start();
15 t1.join();
16
17 synchronized (a){
18 System.out.println("main locking===========================");
19 System.out.println(ClassLayout.parseInstance(a).toPrintable());//轻量锁
20 }
21 }
22 }
23
24
25 thread1 locking===========================
26 BiasedLock.A object internals:
27 OFFSET SIZE TYPE DESCRIPTION VALUE
28 0 4 (object header) 05 f8 27 1f (00000101 11111000 00100111 00011111) (522713093)
29 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
30 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
31 12 1 boolean A.flag false
32 13 3 (alignment/padding gap)
33 16 4 java.lang.String A.hy (object)
34 20 4 (loss due to the next object alignment)
35 Instance size: 24 bytes
36 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
37
38 main locking===========================
39 BiasedLock.A object internals:
40 OFFSET SIZE TYPE DESCRIPTION VALUE
41 0 4 (object header) 00 f4 d9 02 (00000000 11110100 11011001 00000010) (47838208)
42 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
43 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
44 12 1 boolean A.flag false
45 13 3 (alignment/padding gap)
46 16 4 java.lang.String A.hy (object)
47 20 4 (loss due to the next object alignment)
48 Instance size: 24 bytes
49 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
(1)第一坨(偏向线程t1的偏向锁)结果:
第一行+第二行:对象头的markword,(一共64位,8字节)
101结尾
偏向了线程的偏向锁对象的对象头的markword特征是:第一个字节以101结尾 + 有线程ID
第三行:对象a的对象头的_klass(压缩,所以是32位,4字节)
第四行:对象a的boolean类型字段(1字节)
第五行:不知道为啥补了3字节,猜测是为了补齐boolean1字节——>4字节(3字节)
第六行:对象a的String类型字段(即String对象的_klass,4字节)
第七行:为了凑齐8字节的倍数,所以补了4字节(4字节)
合计:24字节
(2)第二坨(轻量锁)结果:
第一行+第二行:对象头的markword,(一共64位,8字节)
轻量锁对象的对象头的markword特征是:第一个字节以00结尾 + 指向栈中锁记录LockRecord
第三行:对象a的对象头的_klass(压缩,所以是32位,4字节)
第四行:对象a的boolean类型字段(1字节)
第五行:不知道为啥补了3字节,猜测是为了补齐boolean1字节——>4字节(3字节)
第六行:对象a的String类型字段(即String对象的_klass,4字节)
第七行:为了凑齐8字节的倍数,所以补了4字节(4字节)
合计:24字节
1.4.7 打印:重量锁对象:
(匿名偏向锁 + 两个以上线程竞争 + 同时竞争一个对象)
1 public class PrintObject {
2
3 public static void main(String[] args) throws InterruptedException{
4 Thread.sleep(5000);
5 A a = new A();
6
7 Thread t1 = new Thread(() -> {
8 synchronized (a){
9 System.out.println("thread1 locking===========================");
10 System.out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向锁
11 }
12
13 });
14 t1.start();
15
16 synchronized (a){
17 System.out.println("main locking===========================");
18 System.out.println(ClassLayout.parseInstance(a).toPrintable());//轻量锁
19 }
20 }
21 }
22
23 main locking===========================
24 BiasedLock.A object internals:
25 OFFSET SIZE TYPE DESCRIPTION VALUE
26 0 4 (object header) 9a 66 5a 1c (10011010 01100110 01011010 00011100) (475686554)
27 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
28 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
29 12 1 boolean A.flag false
30 13 3 (alignment/padding gap)
31 16 4 java.lang.String A.hy (object)
32 20 4 (loss due to the next object alignment)
33 Instance size: 24 bytes
34 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
35
36 thread1 locking===========================
37 BiasedLock.A object internals:
38 OFFSET SIZE TYPE DESCRIPTION VALUE
39 0 4 (object header) 9a 66 5a 1c (10011010 01100110 01011010 00011100) (475686554)
40 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
41 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
42 12 1 boolean A.flag false
43 13 3 (alignment/padding gap)
44 16 4 java.lang.String A.hy (object)
45 20 4 (loss due to the next object alignment)
46 Instance size: 24 bytes
47 Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
(1)第一坨(可能因为main线程优先级较高,所以main和t1竞争时总是main先执行,与第二坨指向同一个Monitor)
第一行+第二行:对象头的markword,(一共64位,8字节)
10结尾 + 指向指向管程Monitor的指针
第三行:对象a的对象头的_klass(压缩,所以是32位,4字节)
第四行:对象a的boolean类型字段(1字节)
第五行:不知道为啥补了3字节,猜测是为了补齐boolean1字节——>4字节(3字节)
第六行:对象a的String类型字段(即String对象的_klass,4字节)
第七行:为了凑齐8字节的倍数,所以补了4字节(4字节)
合计:24字节
(2)第二坨(可能因为main线程优先级较高,所以main和t1竞争时总是t1后执行,与第一坨指向同一个Monitor)
第一行+第二行:对象头的markword,(一共64位,8字节)
10结尾 + 指向指向管程Monitor的指针
第三行:对象a的对象头的_klass(压缩,所以是32位,4字节)
第四行:对象a的boolean类型字段(1字节)
第五行:不知道为啥补了3字节,猜测是为了补齐boolean1字节——>4字节(3字节)
第六行:对象a的String类型字段(即String对象的_klass,4字节)
第七行:为了凑齐8字节的倍数,所以补了4字节(4字节)
合计:24字节
1.4.8 总结一下各种锁的创建场景:
无锁:
程序启动5s以内创建一个对象a即可。
无锁的特点:markWord的第一个字节是001。
匿名偏向锁:
程序启动5s以后创建一个对象a即可。(可以先让程序sleep五秒)
匿名偏向锁的特点:markWord的第一个字节是101 + 无线程ID
偏向于线程的偏向锁:
匿名偏向锁的基础上对对象a加一个Synchronized。
匿名偏向锁的特点:markWord的第一个字节是101 + 有线程ID
轻量级锁:
方式1:main线程里被Thread1,join();Thread1里面是对象a偏向于线程Thread1的偏向锁。等Thread1执行完,markWord中依然存的是Thread1的ID,
此时Main线程对于对象a加一个synchronized,则对象a的锁升级为由Main线程持有的轻量级锁。
方式2:无锁状态时对对象a加一个synchronized即可。
轻量级锁的特点:markWord的第一个字节是00 + 指向栈中LockRecord记录
重量级锁:
在轻量级锁的基础上,开启另一个线程对对象a加上synchronized。此种场景为两个线程同时synchronized对象a,
而轻量级锁的方式1营造的场景是Thread1偏向锁执行完成后Main线程再加一个synchronized。
重量级锁的特点:markWord的第一个字节是10 + 指向被锁对象的monitor