java中简述对象的组成部分,java对象结构解析

对象的基本结构

了解对象的结构对以后的学习有很大的作用,本文主要对64位虚拟机下的对象进行剖析。

对象在创建的时候都有一个对象头,里面包含了对象的基本信息,一个对象的结构如下:

62da3269b46a

对象的组成.png

数组的本质也是对象,他的组成和这个类似,如下:

62da3269b46a

数组的组成.png

在进行探讨之前,我们先来看看实例

先定义一个user类

public class User {

private String name;

private int id;

private int sex;

private boolean finish;

}

maven引入依赖

org.openjdk.jol

jol-core

0.9

执行代码

public class Header {

static User u = new User();

static User[] users = new User[10];

public static void main(String[] args) {

System.out.println(ClassLayout.parseInstance(u).toPrintable());

System.out.println(ClassLayout.parseInstance(users).toPrintable());

}

}

运行结果

62da3269b46a

运行结果.png

解析上面图片(图片当时编辑的时候没注意,有错误,懒得修改了,里面的对象头应该为MarkWord,前三行才叫对象头,数组还需要加上长度)

62da3269b46a

解析.png

首先解释一下上面的一些术语,在java的对象中,对象的大小都为8byte的倍数。

alignment/padding gap

alignment 对齐,对齐都是向8byte对齐

padding 补齐,补齐是向4byte补齐,对象对齐的最小粒度为4byte。

从上图看user对象和数组中前两行为MarkWord,第三行为KlassPointer,这三行加起来为对象头,后面的对象属性中因为int和String为4Byte,所以分配了4Byte,而boolean的内存占用为1Byte,根据java中必须为8的倍数,所以先给boolean补齐为4byte,最后在判断大小是不是8的倍数,如果不是就会进行填充,例如上面的loss due to the next object aligment。从这个对象结构看,我们浪费了7byte的空间。而User数组多了一个长度用来存储数组的长度,后面的数组数据用来存储User的对象指针,通过上面我们就可以知道为什么数组的最大长度为2^31次方了,因为数组长度分配了4byte的空间来储存。

通过上面可能又有疑问了,User类的属性顺序明明为name、id、sex、finish,为什么打印出来的不一样? 这是因为jvm在Heap中给对象布局的时候,会对field进行重排序,用来节省空间,上面的User类只有finish不是4byte,所以我们看不出效果,当我们有的Boolean、byte、char等类型不为4byte的属性时,如果不进行重排序的话我们会浪费很多的空间。

math?formula=%5Ccolor%7Brgb(255%2C0%2C0)%7D%7B%E4%B8%80%E8%88%AC%E6%83%85%E5%86%B5%E4%B8%8B%EF%BC%8Cfield%E5%88%86%E9%85%8D%E7%9A%84%E4%BC%98%E5%85%88%E4%BE%9D%E6%AC%A1%E9%A1%BA%E5%BA%8F%E6%98%AF%EF%BC%9Adouble%20%3E%20long%20%3E%20int%20%3E%20float%20%3E%20char%20%3E%20short%20%3E%20byte%20%3E%20boolean%20%3E%20object%20reference%E3%80%82%7D

通过上面这句话就知道为什么会有alignment/padding了。

对象头

64位对象头由Mark Word、klass pointer两部分组成,如果对象是数组,则还要加上数组长度,即三部分组成。

Mark Word和klass pointer以及length都由64位8个字节组成。由于64位jvm默认使用选项 +UseCompressedOops 开启指针压缩,所以我们看到klass pointer和length只有32位。数组长度和对象指针这里不做描述,懂得都懂,重点讲一下Mark Word

|--------------------------------------------------------------------------------------------------------------|

| 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

|--------------------------------------------------------------------------------------------------------------|

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

lock: 锁状态标记位,该标记的值不同,整个mark word表示的含义不同。

biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁

62da3269b46a

锁.png

age:Java GC标记位对象年龄,因为用四个bit来储存,所以范围为0-15,因此对象经过了15次垃圾回收后如果还存在,则肯定会移动到老年代中。

identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用System.identityHashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。

thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。

epoch:偏向时间戳。

ptr_to_lock_record:指向栈中锁记录的指针。

ptr_to_heavyweight_monitor:指向线程Monitor的指针。

具体解析

在分析上表对象结构的时候需要先明白System.identityHashCode()和hashCode()的区别。他们都是生成对象的hashCode。看下面代码

String str = new String("test");

String str2 = new String("test");

System.out.println(str.hashCode());

System.out.println(str2.hashCode());

System.out.println(System.identityHashCode(str));

System.out.println(System.identityHashCode(str2));

输出

62da3269b46a

结果.png

可以看出hashCode()和System.identityHashCode()得到的值不一样并且str和str2的hashCode()得到的hash是一样的,通过后面那种方法得到的是不一样的。

System.identityHashCode()是通过地址来生成hashCode,因为str和str2都是new出来的,所以他们通过其得到的不一样,而String类重写了hashCode,让他通过内容来生成hashCode,所以str和str2通过hashCode()得到的hashCode是一样的。而System.identityHashCode()和HashCode()得到的结果不一样的原因同上,System.identityHashCode()通过地址,String中的HashCode()通过内容。

然后我们就来看看无锁状态下的对象头储存的信息。

public class Header {

static User u = new User();

static User[] users = new User[10];

public static void main(String[] args) {

System.identityHashCode(u);

System.out.println(ClassLayout.parseInstance(u).toPrintable());

System.out.println(Integer.toBinaryString(System.identityHashCode(u)));

}

}

62da3269b46a

结果.png

无锁状态的对象信息在上面已经标注出,hashCode可以进行比对,具体的GC信息可以通过执行System.gc()验证。

各种锁状态下的对象头的具体分析等以后深入学习了并发后再回来补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java线程对象的状态可分为以下六种: 1. 新建状态(New):当线程对象被创建时,它处于新建状态。 2. 就绪状态(Runnable):当线程对象调用start()方法后,它进入就绪状态。此时线程并没有开始执行,只是等待获取CPU资源。 3. 运行状态(Running):当获取CPU资源后,线程开始执行,进入运行状态。 4. 阻塞状态(Blocked):当线程因为某些原因暂时失去了CPU资源时,进入阻塞状态。例如,线程因为等待IO操作或者调用了sleep()方法。 5. 等待状态(Waiting):当线程因为等待某个条件而进入等待状态,例如调用了wait()方法。 6. 终止状态(Terminated):线程执行完毕或者因为异常终止时,进入终止状态。 线程状态的转换关系如下: 1. 新建状态 -> 就绪状态:当线程对象被创建时,它处于新建状态。当调用start()方法后,线程进入就绪状态。 2. 就绪状态 -> 运行状态:当获取CPU资源后,线程开始执行,进入运行状态。 3. 运行状态 -> 就绪状态:当线程执行完毕或者因为调用了yield()方法而放弃CPU资源时,线程进入就绪状态。 4. 运行状态 -> 阻塞状态:当线程因为某些原因暂时失去了CPU资源时,进入阻塞状态。 5. 阻塞状态 -> 就绪状态:当线程等待的条件满足时,例如IO操作完成或者sleep()时间到了,线程进入就绪状态。 6. 运行状态 -> 等待状态:当线程因为等待某个条件而进入等待状态,例如调用了wait()方法。 7. 等待状态 -> 就绪状态:当线程等待的条件满足时,例如调用了notify()或者notifyAll()方法,线程进入就绪状态。 8. 运行状态 -> 终止状态:当线程执行完毕或者因为异常终止时,进入终止状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值