Java对象的内存布局

3 篇文章 0 订阅

Java对象的内存布局

在介绍Java对象的内存布局之前先介绍一下Java中哪些数据是以对象的形式创建的

  • Java的数据类型
    • 基本数据类型
      • byte、short、int、long、float、double、char、boolean
      • 基本数据类型被创建时,在栈上给其划分一块内存,将数值直接存储在栈上
    • 引用数据类型
      • 类、接口、数组、枚举、注解等
      • 引用数据类型在创建时首先要在栈上给其引用分配一块内存,将对象的实例存储在堆内存上,栈上面的引用指向堆内存中对象的地址

Java中在内存布局时主要考虑了两种内存布局,一种是普通对象,就是类、接口、枚举、注解等的实例,一种是数组对象,即数组的实例

Java普通对象的内存布局

  • Java普通对象的内存布局图如下

    image-20201026201454248

  • markword

    用来标记锁信息、GC信息、IdentityHashCode信息等,32位JVM是4字节,64位JVM中是8字节

    在openjdk的源码包中(/openjdk/hotspot/src/share/vm/oops)可以找到对应的C++markOop.hpp文件,从注释中可以看到markword分别在32位和64位JVM中的组成
    image-20201027094452529
    具体组成见下面两张图
    32位JVM中markword的组成
    image-20201027143932009
    64位JVM中markword的组成
    image-20201027144002982
    虽然它们在不同位数的JVM中长度不一样,但是基本组成内容是一致的。

    • 锁标志位(lock):区分锁状态,11时表示对象待GC回收状态, 只有最后2位锁标识(11)有效。
    • biased_lock:是否偏向锁,由于正常锁和偏向锁的锁标识都是 01,没办法区分,这里引入一位的偏向锁标识位。
    • 分代年龄(age):表示对象被GC的次数,当该次数到达阈值的时候,对象就会转移到老年代。
    • 对象的hashcode(hash):运行期间调用System.identityHashCode()来计算,延迟计算,并把结果赋值到这里。当对象加锁后,计算的结果31位不够表示,在偏向锁,轻量锁,重量锁,hashcode会被转移到Monitor中。
    • 偏向锁的线程ID(JavaThread):偏向模式的时候,当某个线程持有对象的时候,对象这里就会被置为该线程的ID。 在后面的操作中,就无需再进行尝试获取锁的动作。
    • epoch:偏向锁在CAS锁操作过程中,偏向性标识,表示对象更偏向哪个锁。
    • ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。当锁获取是无竞争的时,JVM使用原子操作而不是OS互斥。这种技术称为轻量级锁定。在轻量级锁定的情况下,JVM通过CAS操作在对象的标题字中设置指向锁记录的指针。
    • ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。如果两个不同的线程同时在同一个对象上竞争,则必须将轻量级锁定升级到Monitor以管理等待的线程。在重量级锁定的情况下,JVM在对象的ptr_to_heavyweight_monitor设置指向Monitor的指针。
  • klass pointer

    类指针,用于标记该对象是哪一个Class类对象(Object.class)的实例

    JVM可以选择开启和关闭指针压缩,JVM是64位操作系统的话,开启后4字节,不开启8字节,默认开启。

    设置指针内存压缩的指令为-XX:+UseCompressedOops

    取消指针内存压缩的指令为-XX:-UseCompressedOops

  • instance data

    大小由成员变量的类型和数量决定的,如果没有成员变量则这里为空,存放基本变量或变量引用

  • padding

    一个对象占用的字节数默认必须是8的倍数,不足用padding补齐。

    若没有引用(new Object()),则直接判断是否被8整除,不足的用padding补齐

    若存在引用(Object obj = new Object()),则将引用对应的4字节加上,然后判断是否被8整除,不足用padding补齐

    JVM可以设置更改默认值8,使用指令-XX:ObjectAlignmentInBytes

Java数组对象的内存布局

  • Java数组对象的内存布局图如下

    image-20201026215247954

  • 数据对象的内存布局和普通对象内存布局基本类似,数组对象的内存布局Header中多了一个数组长度length

  • length

    标记数组有多少个元素,4字节

  • instance data

    根据数组类型m和数组n长度决定的,占m*n字节

    如果数组元素为基本数据类型,byte/boolean/…,m为对应的长度

工具包JOL(Java Object Layout)

  • 可以查看JVM中对象的内存布局

  • 在maven中添加引用

    <dependency>
        <groupId>org.openjdk.jol</groupId>
        <artifactId>jol-core</artifactId>
        <version>0.7.1</version>
    </dependency>
    
  • 案例1,测试没有成员变量情况

    import org.openjdk.jol.info.ClassLayout;
    public class Test{
        public static void main(String[] args){
            Object obj = new Object();
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }
    

    输出结果为

    image-20201027092507391

    因为没有成员变量,因此前两个header是markword,第三个header是压缩后的Klass pointer,因为加起来是12字节,所以需要paading补齐4字节

  • 案例2,测试含有成员变量的情况

    import org.openjdk.jol.info.ClassLayout;
    public class Test{
        public static void main(String[] args){
            Obj obj = new Obj();
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }
    

    输出结果为

    image-20201027092408432

    因为含有成员变量,因此前两个header是markword,第三个header是压缩后的Klass pointer,第四行是成员变量的大小4字节,因为加起来刚好是16字节,所以不需要paading补齐

  • 案例3,测试有锁的情况

    import org.openjdk.jol.info.ClassLayout;
    public class Test{
        public static void main(String[] args){
            Object obj = new Object();
            synchronized(obj){
            	System.out.println(ClassLayout.parseInstance(obj).toPrintable());    
            }
        }
    }
    

    输出结果为

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OrOUEYpz-1603781321223)(https://raw.githubusercontent.com/EsonW/picgo/master/image-20201027092601905.png)]

    因为没有成员变量,因此前两个header是markword,第三个header是压缩后的Klass pointer,因为加起来是12字节,所以需要paading补齐4字节

    第一行的value可以看到,和没有锁的是不一样的,说明markword里保存了锁的相关信息

  • 案例4,测试数组的情况

    import org.openjdk.jol.info.ClassLayout;
    public class Test{
        public static void main(String[] args){
            int[] arr = {1, 2, 3, 4};
            System.out.println(ClassLayout.parseInstance(arr).toPrintable());
        }
    }
    

    输出结果为

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2tvJh59i-1603781321226)(C:%5CUsers%5CAdministrator%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20201027093113564.png)]

    因为没有成员变量,因此前两个header是markword,第三个header是压缩后的Klass pointer,第四个header是数组长度length,数组元素占16字节,因为加起来是32字节,所以不需要paading补齐

  • 案例5,测试取消指针压缩,值JVM指令-XX:-UseCompressedOops

    import org.openjdk.jol.info.ClassLayout;
    public class Test{
        public static void main(String[] args){
            Object obj = new Object();
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }
    

    image-20201027094055470

    因为没有成员变量,因此前两个header是markword,第三四个header是未压缩的Klass pointer,因为加起来是16字节,所以不需要paading补齐

参考链接

https://www.cnblogs.com/JonaLin/p/13864578.html

https://www.cnblogs.com/zxj-study/p/13057490.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值