JAVA 对象到底有什么
我们用Java来编写程序,经常创建对象,那么Java中的对象到底包含什么东西呢?
JAVA 对象包括:
1 对象头
2 对象的实例数据
3 对齐填充
对象头里面都有什么呢?
一、看看openjdk官方文档怎么说的地址:http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
二、文档对于object header的描述:
Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object's layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.
翻译过来:
每一个GC管理的堆对象的普通架构,(每一个oop指针指向的对象头)包括堆对象的布局的基本信息,类型,GC状态,syn锁状态和hash code,由2个words组成,如果是数组则会跟着数组的长度字段,java对象和VM内部对象有着相同的头格式。
三、那么其中的2words是什么呢?一个word为:- mark word
The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.
翻译过来:
每个对象头的第一个word通常是一些列的 位字段,包括 锁状态、hash code,也可能是指向 synchronization相关的信息的指针,在GC过程中,也会包含GC状态位
四、第二个word为- klass pointer
The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the "klass" contains a C++ style "vtable".
翻译过来:
每个对象的第二个word为一个指针,指向对象关联的类信息
五、对象头的具体组成结构 ,从源码中获取 - >openjdk\src\hotspot\share\oops\markWord.hpp - 64位机器组成结构
// 64 bits:
// --------
// unused:25(未使用25位) hash:31(hashcode占用31位) -->| unused_gap:1(未使用) age:4(分代年龄占4位,最大值为15因此分代年龄最大为15) biased_lock:1(偏向锁1位) lock:2(锁占2位) (normal object) -- 普通的对象
// JavaThread*:54 epoch:2 unused_gap:1 age:4 biased_lock:1 lock:2 (biased object) -- 带有锁的对象
我们的计算机是小端存储则打印出来的数据信息顺序为:小端存储( 低字节在前 高字节在后) ,大端存储(高字节在前 小字节在后)
六、打印对象头信息
1)代码中引入工具类
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>
2)打印类信息
测试类
package com.test;
public class B {
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import org.openjdk.jol.info.ClassLayout; /** * Hello world! *-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode -XX:-UseCompressedOops */ public class Test3 { static B b = new B(); public static void main( String[] args ) { System.out.println(ClassLayout.parseInstance(b).toPrintable()); // System.out.println( Integer.toHexString( b.hashCode()) ); // System.out.println(VM.current().details()); // System.out.println(ClassLayout.parseClass(B.class).toPrintable()); // System.out.println(ClassLayout.parseInstance(b).toPrintable()); } }
3)打印的结果
com.test.B 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 (loss due to the next object alignment)
4)结果分析
-
最后打印的头信息为12个字节,理论上应该有16个字节(因为jvm开启了指针压缩的原因,可以加上参数去掉指针压缩来查看-XX:-UseCompressedOops )
-
最后的4个字节不是头信息-是字节填充信息 - 对象储存必须是8字节的倍数,不满足则自动填充
六、对象头-验证hash存储 - 打印的hash值是头中的 2-5字节,并且显示是反方向(小端存储)
![](https://img2020.cnblogs.com/blog/512833/202009/512833-20200922181432571-57363787.png)
2) 低端存储展示(数据要反过来才能看明白), 分年年龄 、是否偏向 、锁标识 位置
七、锁标识验证
无锁不可偏向001 - 对象打印了对象的hash,则前56位存储了对象的hash,不能存储锁对应的线程id
偏向锁已偏向(101) - 前56位存储了锁线程的id,标识位显示偏向锁
轻量级锁(00), - 前56位存储了锁线程的地址,标识位标识为 轻量级锁
重量级锁(10), GC(11) - 前56位存储了锁线程的地址,标识位标识为 重量级的锁
- 当一把锁第一次被线程持有的时候是偏向锁,如果这个线程再次加锁还是偏向锁
- 如果另一个线程也来加锁(交替执行),膨胀为轻量锁
- 如果另一个线程在等待第一个线程释放锁(锁竞争),则重量级锁
八、代码展示锁标识变化
1)演示轻量级锁
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.test;//package com.test; import org.openjdk.jol.info.ClassLayout; /** * Hello world! *-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode -XX:-UseCompressedOops * -Xms1g -Xmx1g -XX:+PrintGCDetails */ public class Test3 { // static B b = new B(); public static void main( String[] args ) throws InterruptedException { B b = new B(); // b.hashCode(); // System.out.println(ClassLayout.parseInstance(b).toPrintable()); // System.out.println( Integer.toHexString( b.hashCode()) ); // synchronized (b){ // System.out.println(ClassLayout.parseInstance(b).toPrintable()); // } Test test1 = new Test(b); Thread t1 = new Thread(test1); // Test test2 = new Test(b); // Thread t2 = new Thread(test2); t1.start(); // t1.join(); // t2.start(); // System.out.println(ClassLayout.parseInstance(b).toPrintable()); // System.out.println(VM.current().details()); // System.out.println(ClassLayout.parseClass(B.class).toPrintable()); } public static class Test implements Runnable{ private B b ; public Test(B b) { this.b = b; } @Override public void run() { synchronized (b){ System.out.println(ClassLayout.parseInstance(b).toPrintable()); } } } }
2)演示重量级锁,上面代码加上:
Test test2 = new Test(b); Thread t2 = new Thread(test2); t2.start();
![](https://img2020.cnblogs.com/blog/512833/202009/512833-20200922181630139-1555944459.png)
3)演示偏向锁,需要加上下面“关闭偏向锁延迟”
- JVM参数修改
//关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
//禁止偏向锁
-XX:-UseBiasedLocking
//启用偏向锁
-XX:+UseBiasedLocking
- 线程代码修改,在对象加锁前,加锁时,加锁后分别打印头信息,观察头信息变化,代码为:
public void run() { //加锁前 System.out.println(ClassLayout.parseInstance(b).toPrintable()); synchronized (b){ System.out.println(ClassLayout.parseInstance(b).toPrintable()); } //加锁后 System.out.println(ClassLayout.parseInstance(b).toPrintable()); }