java对象结构解析

对象的基本结构

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

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


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


    数组的组成.png

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

先定义一个user类

public class User {
    private String name;
    private int id;
    private int sex;
    private boolean finish;
}

maven引入依赖

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

执行代码

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());
    }
}

运行结果


运行结果.png

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


解析.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的属性时,如果不进行重排序的话我们会浪费很多的空间。
\color{rgb(255,0,0)}{一般情况下,field分配的优先依次顺序是:double > long > int > float > char > short > byte > boolean > object reference。}
通过上面这句话就知道为什么会有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时表示对象没有偏向锁


锁.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));

输出


结果.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)));
    }
}
结果.png

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容