博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
聊聊Java对象在内存中的大小
阅读量:6861 次
发布时间:2019-06-26

本文共 10233 字,大约阅读时间需要 34 分钟。

聊聊Java对象在内存中的大小

本文讨论的Java对象在内存中的大小指的是在堆(Heap)中的大小;未特殊说明,提到JVM的地方都指的是:Java HotSpot(TM) 64-Bit Server VM,版本:1.8.0_131

  • Java中Object的组成:

    Object = Header + Primitive Fields + Reference Fields + Alignment & Padding`

    1. Header由两部分组成:标记部分(Mark Word)和原始对象引用(Klass Pointer/Object Original Pointer)- 。

      • 标记部分的大小是一个word size(64-bit JVM上是8 bytes,32-bit JVM上是4 bytes),包括了该对象的identity hash code和一些标记(比如锁和年代信息)。
      • 原始对象引用在32-bit JVM上的大小是4 bytes,在64-bit JVM上可以是4 bytes,也可以是8 bytes,由JVM参数“是否压缩原始对象”决定,在HotSpot中是UseCompressedOops参数(jdk1.8 和jdk1.9默认是开启的)。
    2. Primitive Fields && Reference Fields

      类型 大小
      Object Reference word size
      byte 1 byte
      boolean 1 byte
      char 2 bytes
      short 2 bytes
      int 4 bytes
      float 4 bytes
      double 8 bytes
      long 8 bytes
    3. 对齐(Alignment)和补齐(Padding)

      • 对齐,任何对象都是以8 bytes的粒度来对齐的

        怎么理解这句话呢?请看一个例子,new Object()产生的对象的大小是多少呢?12 bytes的header,但对齐必须是8的倍数,还有4 bytes的alignment,所以对象的大小是16 bytes.

      • 补齐,补齐的粒度是4 bytes

      • 可以简单理解为,JVM分配内存空间一次最少分配8 bytes,对象中字段对齐的最小粒度为4 bytes
  • 准备工作

    本文使用Maven管理Jar包,源码在。

    1. pom.xml中引入(Java Object Layout, )依赖,用于展示对象在Heap中的分布(layout):

      org.openjdk.jol
      jol-core
      0.9
    2. 第一个测试:

      public static void main(String[] args) {    System.out.println(VM.current().details());}

      执行后,会输出:

      # Running 64-bit HotSpot VM.# Using compressed oop with 3-bit shift.# Using compressed klass with 3-bit shift.# WARNING | Compressed references base/shifts are guessed by the experiment!# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.# Objects are 8 bytes aligned.  // 以 8 bytes的粒度对齐# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]    // 分别对应[Oop(Object Original Pointer), boolean, byte, char, short, int, float, long, double]的大小# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]    // 数组中元素的大小,分别对应的是[Oop(Object Original Pointer), boolean, byte, char, short, int, float, long, double]
  • 对象在Heap中的分布遵循的规则:

    1. 重排序, JVM在Heap中给对象布局时,会对field进行重排序,以节省空间。

      例-1,对于类:

      public class Reorder {    private byte a;    private int b;    private boolean c;        private float d;    private Object e;        public static void main(String[] args) {        System.out.println(ClassLayout.parseClass(Reorder.class).toPrintable());    }}

      如果没有重排序,对象的分布会是这个样子的:

      objectsize.Reorder object internals: OFFSET  SIZE      TYPE DESCRIPTION                               VALUE      0    12           (object header)                           N/A     12     1      byte Reorder.a                                 N/A     13     3           (alignment/padding gap)                       16     4       int Reorder.b                                 N/A     20     1   boolean Reorder.c                                 N/A     21     3           (alignment/padding gap)                       24     4     float Reorder.d                                 N/A     28     2      char Reorder.e                                 N/A     30     2           (loss due to the next object alignment)Instance size: 32 bytesSpace losses: 6 bytes internal + 2 bytes external = 8 bytes total

      对象实例总大小:32 bytes,空间损失:8 bytes。

      而实际是(运行main方法会看到结果):

      objectsize.Reorder object internals: OFFSET  SIZE      TYPE DESCRIPTION                               VALUE      0    12           (object header)                           N/A     12     4       int Reorder.b                                 N/A     16     4     float Reorder.d                                 N/A     20     2      char Reorder.e                                 N/A     22     1      byte Reorder.a                                 N/A     23     1   boolean Reorder.c                                 N/AInstance size: 24 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total

      对象实例总大小:24 bytes,空间损失:0 bytes。

    2. 为了避免空间浪费,一般情况下,field分配的优先依次顺序是:double > long > int > float > char > short > byte > boolean > object reference

      注意到了没,这里有个基本的原则是:尽可能先分配占用空间大的类型(除了object reference)。这里的尽可能有两层含义:

      • 在同等优先级情况下,按这个顺序分配。 例-2

        public class Order {        private int ignoreMeTentatively;        private byte a;        private boolean b;        private char c;        private short d;        private int e;        private float f;        private double g;        private long h;        private Object i;        public static void main(String[] args) {        System.out.println(ClassLayout.parseClass(Order.class).toPrintable());    }}

        这个类的实例在内存中分布是:

        objectsize.Reorder object internals: OFFSET  SIZE      TYPE DESCRIPTION                               VALUE      0    12           (object header)                           N/A     12     4       int Reorder.b                                 N/A     16     4     float Reorder.d                                 N/A     20     2      char Reorder.e                                 N/A     22     1      byte Reorder.a                                 N/A     23     1   boolean Reorder.c                                 N/AInstance size: 24 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total

        请先忽略ignoreMeTentatively字段,可以验证类型分配的顺序。

      • 在考虑到补齐(Padding)的情况下,排在后面的类型有可能比排在前面的优先级更高。

        回过头来看例-1例-2,会发现header后的字一个field(offset 12)都是int类型的。为什么呢?

        这就是AlignmentPadding共同作用的结果。

        JVM每次最少分配8 bytes的空间,而header的大小是12。

        也就是说,已经分配了16 bytes的空间了,如果严格按照前面说的那个顺序,最先分配一个double类型的field,就需要在这之前先分配4 bytes的空间来补齐,也就这4 bytes的空间就白白浪费了。
        这中情况下,<=Padding Size(4 bytes)的类型的优先级就高于大小>Padding Size的类型了。
        而在所有大小<=Padding Size的类型中,int的优先级又是最高的,所以header后的第一个field是int

        为了进一步理解,再来看个例子,例-3

        public class Padding {          private char a;          private boolean b;          private long c;          private Object d;      public static void main(String[] args) {          System.out.println(ClassLayout.parseClass(Padding.class).toPrintable());   }}

        这个类的实例在内存中分布是:

        objectsize.Padding object internals: OFFSET  SIZE               TYPE DESCRIPTION                               VALUE      0    12                    (object header)                           N/A     12     2               char Padding.a                                 N/A     14     1            boolean Padding.b                                 N/A     15     1                    (alignment/padding gap)                       16     8               long Padding.c                                 N/A     24     4   java.lang.Object Padding.d                                 N/A     28     4                    (loss due to the next object alignment)Instance size: 32 bytesSpace losses: 1 bytes internal + 4 bytes external = 5 bytes total

        可以看到header后的4个bytes空间分配情况,在所有大小<=Padding Size的类型中,char的优先级最高,其次是boolean

        这两个加起来只有3 bytes(<Padding Size),而已经没有1 byte大小的field了,所以只能分配1 byte的Padding。
        接下来,JVM再分配一个8 bytes大小的空间,很明显空间足够的情况下,long的优先级最高,也正好用完这8 bytes的空间。
        然后,JVM继续分配一个8 bytes大小的空间,最后一个类型object reference(这里是Object)了,在开启UseCompressedOops的情况下,使用4 bytes的空间,还有4 bytes的空间只能用来对齐了。

    3. 子类和父类的field永远不会混合在一起,并且父类的field分配完之后才会给子类的field分配空间。

      例-4

      public class SuperA {        long a;        private int b;        private float c;            private char d;            private short e;}public class SubA extends SuperA {        private long d;    public static void main(String[] args) {        System.out.println(ClassLayout.parseClass(SubA.class).toPrintable());    }    }

      SubA的实例在内存中的分布是:

      objectsize.SubA object internals:   OFFSET  SIZE    TYPE DESCRIPTION                               VALUE        0    12         (object header)                           N/A       12     4     int SuperA.b                                  N/A       16     8    long SuperA.a                                  N/A       24     4   float SuperA.c                                  N/A       28     2    char SuperA.d                                  N/A       30     2   short SuperA.e                                  N/A       32     8    long SubA.d                                    N/AInstance size: 40 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total

      父类SuperA中的field全部分配完后,才分配子类SubAfield

    4. 父类的的最后一个字段与子类的第一个字段以一个Padding Size(4 bytes)来对齐。

      例-5

      public class SuperB {        private byte a;        private int b;}public class SubB extends SuperB {        private int a;        private long b;    public static void main(String[] args) {        System.out.println(ClassLayout.parseClass(SubB.class).toPrintable());    }}

      SubB的实例在内存中分布是:

      objectsize.SubB object internals:  OFFSET  SIZE   TYPE DESCRIPTION                               VALUE       0    12        (object header)                           N/A      12     4    int SuperB.b                                  N/A      16     1   byte SuperB.a                                  N/A      17     3        (alignment/padding gap)                        20     4    int SubB.a                                    N/A      24     8   long SubB.b                                    N/AInstance size: 32 bytesSpace losses: 3 bytes internal + 0 bytes external = 3 bytes total

      从offset 16的位置开始看,父类还有最后一个字段a未分配,这时JVM分配一个8 bytes的空间,a占用1 byte,

      还有7 bytes未使用,而这7 bytes空间没有全部用于对齐,也就是说子类字段的分配并不是从offset 24 开始的。
      实际上只用了3 bytes空间来对齐(凑够4 bytes的Padding Size),剩下的4 bytes分配给了子类的a字段。

    5. 数组也是对象,但数组的header中包含有一个int类型的length值,又多占了4 bytes的空间,所以数组的header大小是16 bytes。

      例-6

      public class ArrayTest {    public static void main(String[] args) {        System.out.println(ClassLayout.parseInstance(new boolean[1]).toPrintable());    }}

      长度为1的boolean数组的实例在内存的分布是:

      [Z 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 00 00 f8 (00000101 00000000 00000000 11111000) (-134217723)   12     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)   16     1   boolean [Z.
      N/A 17 7 (loss due to the next object alignment)Instance size: 24 bytesSpace losses: 0 bytes internal + 7 bytes external = 7 bytes total

      可以看到,header占用了16 bytes,一个boolean元素占用了1 bytes,剩余7 bytes用于对齐。

  • 参考资料

转载地址:http://hnhyl.baihongyu.com/

你可能感兴趣的文章
安卓多线程的实现
查看>>
【现在还没补的比赛及题解】
查看>>
C#截取字符串按字节截取SubString
查看>>
MAVLink v1.0详解——结构
查看>>
Office 365离线安装
查看>>
服务器负载暴涨以后...
查看>>
【物联网智能网关-15】WAV播放器(WinForm+WavPlay库实例)
查看>>
实战:将静态路由发布到动态路由
查看>>
Linux桌面新彩虹-Fedora 14 炫酷应用新体验
查看>>
灵活管理Hadoop各发行版的运维利器 - vSphere Big Data Extensions
查看>>
Data Protection Manager 2010 系列之安装部署
查看>>
【SeaJS】【3】seajs.data相关的源码阅读
查看>>
[PHP] 访问MySQL
查看>>
linux下redmine3.3迁移、升级、插件备忘录
查看>>
Hadoop原理及部署初探
查看>>
Oracle 11g R2 常见问题处理
查看>>
windows下expdp自动备份脚本
查看>>
WPF-009:WPF窗体的拖动
查看>>
MDT2012部署系列之10 Win7镜像捕获与系统安装
查看>>
Windows 2003 AD升级至Windows 2012 AD之DHCP服务器迁移
查看>>