美文网首页程序员老赵java架构师之路
java对象内存布局模型以及内存大小详解

java对象内存布局模型以及内存大小详解

作者: l只为终点 | 来源:发表于2020-05-19 17:06 被阅读0次

    今天和大家简单介绍下java对象在内存中的布局,以及一个对象在内存中所占的大小,我们以前都了解过比如一个int在java中占4个byte,一个boolean占用一个字节,那么如果是一个Integer对象到底占用多少字节了?影响一个对象在内存中占用大小的因素都有哪些了?希望通过这篇文章能给大家一个比较清晰的认识。

    内存存储模型.png

    在hotspot中,一个java对象由对象头、实例数据和对象填充三部分组成,对象头又由对象标记、类元信息、数组长度(只有数组对象才有)组成。在HotSpot源码中,instanceOop.hpp类中

    #include "oops/oop.hpp"
    
    // An instanceOop is an instance of a Java Class
    // Evaluating "new HashTable()" will create an instanceOop.
    
    class instanceOopDesc : public oopDesc {
     public:
      // aligned header size.
      static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; }
      ...
     }
    

    我们可以看到所有的java对象都会创建一个instanceOop对象

    对象头

    通过上面的源码,我们可以看到instanceOopdesc继承oopDesc,我们查看oopDesc.hpp代码查看到:

    class oopDesc {
      friend class VMStructs;
     private:
      volatile markOop  _mark; // 对象标记
      union _metadata {
        Klass*      _klass; // 普通指针
        narrowKlass _compressed_klass; // 压缩指针
      } _metadata; // 类元信息
    ... 
    

    可以很清楚的看到顶层Oop中包含了_mark和_metadata,下面我们单独来分析:

    1、对象标记(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.

    ​ 对象标记又称Mark Word,用于存储对象运行时的一些特殊数据,比如HashCode、GC分代年龄、锁状态标志、持有锁的线程Id、偏向锁线程Id、偏向时间等。

    可以通过下图全面的了解对象标记的组成:

    对象头标记组成.png

    对象标记在32位操作系统和64位操作系统(未开启压缩指针)中分别为4byte和8byte,我们可以从hotspot源码中看到如下介绍:

    //  32 bits:
    //  --------
    //             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
    //             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
    //             size:32 ------------------------------------------>| (CMS free block)
    //             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
    //
    //  64 bits:
    //  --------
    //  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
    //  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
    //  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
    //  size:64 ----------------------------------------------------->| (CMS free block)
    

    ​ 我们可以通过在项目中引入openjdk的jol包,来查看对象大小以及对象头中的信息:

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

    我们通过一个简单的例子来查看对象头的大小,我的操作系统是64位,JDK1.8版本,默认是开启指针压缩的:

    public class JolDemo {
    
      public static void main(String[] args) {
        JolDemo jolDemo = new JolDemo();
        synchronized (jolDemo) {
          System.out.println(ClassLayout.parseInstance(jolDemo).toPrintable());
        }
      }
    
    }
    

    执行结果:

    com.laozhao.oop.JolDemo object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           f0 a8 8a 09 (11110000 10101000 10001010 00001001) (160082160)
          4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
          8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    ​ 可以看到对象头为8位MarkWord + 4位KlassPointer = 12位,填充了4位,最后对象大小为16位。这是因为所有对象大小都要凑够8的倍数,如若不够,就填充对齐,比如这里填充了4个字节。

    上面这个例子中,我们特意加入了Synchronized锁,就是为了简单介绍对象头中锁标识的存储信息。可以看到此处在head里第一个8bit 11110000中,最后3位是000,代表使用的是轻量级锁,我们对代码做如下改动:

    public class JolDemo {
    
      public static void main(String[] args) {
        JolDemo jolDemo = new JolDemo();
        synchronized (jolDemo) {
          jolDemo.hashCode();
          System.out.println(ClassLayout.parseInstance(jolDemo).toPrintable());
        }
      }
    
    }
    
    com.laozhao.oop.JolDemo object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           5a d5 83 d2 (01011010 11010101 10000011 11010010) (-763112102)
          4     4        (object header)                           95 7f 00 00 (10010101 01111111 00000000 00000000) (32661)
          8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    可以看到 01011010,最后3位变成了010,代表使用的是重量级锁,仅仅是因为我们调用了对下的hashCode(), 各位知道这里的原因么?

    2、类元信息(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".

    ​ 类元信息是指向对象类元数据的指针,虚拟机通过这个指针来确定我们的对象属于哪个实例

    ​ 类元信息在虚拟机中,32位操作系统长度是4byte,64位是8byte(如果开启了指针压缩,则也是4byte)。

    ​ 可以通过JVM启动参数:-XX:+UseCompressedOops来启用指针压缩,这个参数只有64位的Hotspot支持,在jdk1.8中默认是开启的,如果需要禁止指针压缩,直接去掉该参数或者更改成-XX:-UseCompressedOops即可。

    ​ 我们还是用下面的demo,来查看开启/关闭指针压缩的情况:

    public class JolDemo {
    
      public static void main(String[] args) {
        JolDemo jolDemo = new JolDemo();
        System.out.println(ClassLayout.parseInstance(jolDemo).toPrintable());
      }
    
    }
    

    开启指针压缩的结果:

    com.laozhao.oop.JolDemo 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)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    ​ 我们可以看到对象头占了12个字节,填充了4位,对象总大小是16字节。

    关闭指针压缩的结果:

    com.laozhao.oop.JolDemo 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)                           28 70 e9 0f (00101000 01110000 11101001 00001111) (266956840)
         12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    

    关闭指针压缩后可以看到对象头占了16个字节,对象总大小是16字节,此时填充字节数为0.

    3、数组长度(Length)

    ​ 如果是数组对象(objArrayOopDesc或typeArrayOopDesc)则会在对象头中存储数组长度信息。

    我们关闭指针压缩,然后采用如下代码来查看数组长度信息:

    public class JolDemo {
    
      public static void main(String[] args) {
        int[] strs = new int[]{22, 33, 44};
    
        System.out.println(ClassLayout.parseInstance(strs).toPrintable());
      }
    
    }   
    

    运行代码获得如下结果:

     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)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
         12     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
         16    12    int [I.<elements>                             N/A
         28     4        (loss due to the next object alignment)
    Instance size: 32 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    可以看到对象头为8个MarkWord + 4个KlassPointer + 4个数组长度 = 16个字节,数组中有3个int元素,每一个int占用4个字节,总共12个字节,16 + 12 = 28,不足8的倍数,所以填充4个字节。

    综上我们通过图标来梳理对象头在操作系统中占用的字节大小:

    Mark Word Klass Pointer Length
    32位操作系统 4 4 4
    64位未开启指针压缩 8 8 4
    64位开启指针压缩 8 4 4

    对象实际数据

    java对象在内存中的实际大小跟所拥有的成员变量有关,而不受方法和构造函数等的影响。以下是几种基本数据类型在内存中占用的大小情况:

    基本数据类型 占用字节
    boolean 1
    byte 1
    short 2
    char 2
    int 4
    float 4
    long 8
    double 8
    reference 4/8

    我们通过代码来验证:

    public class BasicTypeDemo {
    
      private boolean _boolean = false;
      private byte _byte = 1;
      private char _char = '2';
      private short _short = 2;
      private int _int = 4;
      private float _float = 4;
      private double _double = 8;
      private long _long = 8;
    
      public static void main(String[] args) {
        BasicTypeDemo demo = new BasicTypeDemo();
        System.out.println(ClassLayout.parseInstance(demo).toPrintable());
      }
    
    }
    

    执行结果如下:

    com.laozhao.oop.BasicTypeDemo 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)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
         12     4       int BasicTypeDemo._int                        4
         16     8    double BasicTypeDemo._double                     8.0
         24     8      long BasicTypeDemo._long                       8
         32     4     float BasicTypeDemo._float                      4.0
         36     2      char BasicTypeDemo._char                       2
         38     2     short BasicTypeDemo._short                      2
         40     1   boolean BasicTypeDemo._boolean                    false
         41     1      byte BasicTypeDemo._byte                       1
         42     6           (loss due to the next object alignment)
    Instance size: 48 bytes
    Space losses: 0 bytes internal + 6 bytes external = 6 bytes total
    

    包装类(Boolean/Byte/Short/Character/Integer/Long/Double/Float)占用内存的大小, 等于对象头的大小加上基础数据类型的大小。

    以Boolean为例:

    1. 开启压缩指针

      对象头12位,boolean基础数据占1位,则总实例大小为12 + 1 + 3 = 16

    2. 关闭压缩指针

      对象头16位,boolean基础数据占1位,则总实例大小为16 + 1 + 7 = 24

      所以我们得到包装类类型占用的空间如下表:

      包装类型 开启压缩指针(byte) 关闭压缩指针(byte)
      Byte、BOOLEAN 16 24
      SHORT、CHARACTER 16 24
      INTEGER、FLOAT 16 24
      LONG、DOUBLE 24 24

    那同样我们来用代码简单验证下,以开启压缩Long为例:

    public class BasicTypeDemo {
    
      public static void main(String[] args) {
        Long demo = new Long("2");
        System.out.println(ClassLayout.parseInstance(demo).toPrintable());
      }
    
    }
    
    java.lang.Long 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)                           f4 22 00 f8 (11110100 00100010 00000000 11111000) (-134208780)
         12     4        (alignment/padding gap)                  
         16     8   long Long.value                                2
    Instance size: 24 bytes
    Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
    

    从结果可以看出占用空间是24byte。

    对齐填充

    java对象占用空间都是8的倍数,比如一个对象包含两个属性:int和long,所占空间为4+8=12,不是8的倍数,所以需要填充4个字节变成16,因此此对象的大小是16字节。

    从上面的执行结果中,我们可以看到padding并不是在所有变量的最后填充的,这是因为JVM在变量的声明时会发生重排序,在特定的空间位置会来填充padding来对齐8字节长度,关于hotspot中的alignment,我们后续单独用一篇文章来介绍。

    相关文章

      网友评论

        本文标题:java对象内存布局模型以及内存大小详解

        本文链接:https://www.haomeiwen.com/subject/pngbohtx.html