HotSpot虚拟机中对象的内存布局

对象的大小以及内存布局与虚拟机的实现和设置有很大关系。
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划为三个部分:对象头、实例数据、对齐填充

1 对象头

HotSpot虚拟机对象的对象头(Mark Word)部分包括两类信息,
第一类是用于存储对象自身的运行时数据,另一类是类型指针。

1.1 对象自身的运行时数据

如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度在32位和64位虚拟机(未开启指针压缩)中,分别为4个字节(32bit)和8个字节(64bit)

对象头是一个有动态定义的数据结构。以便在极小的空间内存存储尽量多的数据,根据对象的状态复用存储空间。来节省存储成本和虚拟机的空间效率。

在不同的状态下对象头中所存储的内容会有所不同。

存储内容标志位状态
对象哈希码、对象分代年龄01无锁状态
指向栈中锁记录的指针00轻量级锁
指向互斥量(重量级锁)的指针10重量级锁
空,不记录信息11GC标记
偏向线程ID、偏向时间戳、对象分代年龄01偏向锁,是否偏向锁为1

例如在64位的HotSpot虚拟机中,如对象处于无锁状态下时,对象头的64个字节中的26个bit处于空闲状态,HashCode占了31个bit,4个bit用来描述分代年龄,1个bit固定为0表示不是偏向锁,2个bit用于存储锁标志位。
在这里插入图片描述

1.2 类型指针

对象头的另一类是类型指针,即对象指向它的类型原数据的指针,Java虚拟机通过这个指针来确定对象时那个Class的实例,不是所有的虚拟机实现都需要在对象数据上存储类型指针。如果对象是数组时,在对象头中还需要有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是数组的长度不确定,就无法推断出整个数组的大小,无法分配内存

类指针占用内存大小:
-XX:+UseComparessedClassPointers 开启的话是4个字节,不开启则为8个字节

1.2.1 对象的访问定位

建立对象是为了使用对象,我们通过栈上的reference数据来操作堆上的具体对象。
由于reference类型在java虚拟机规范中只规定了一个指向对象的引用,并没有定义引用应该通过什么方式定位、访问堆中的对象具体位置,所以对象的访问方式取决于虚拟机的实现。
主流的方法分为句柄访问直接指针访问两种,就HotSpot而言主要使用的是第二种直接指针访问进行对象访问(如果使用了ShenandoahGC收集器时会有额外的转发)

  1. 句柄访问
    在使用句柄访问的时候,Java堆中将可能会划分出一块内存在作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自具体的地址信息

    好处:reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不会改变
    在这里插入图片描述

  2. 直接指针访问
    在使用直接指针访问时,Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象实例地址,如果只是访问对象本身的话,就不需要多一次间接的定位开销

    好处:访问速度更快,它节省了一次指针定位的时间开销

在这里插入图片描述

2 实例数据(成员变量)

实例数据部是对象真正存储的有效信息,即我们在代码中定义的各种成员变量

成员变量占用内存的大小:

  • 引用类型:
    -XX:+UseCOmparessedOops 开始的话4个字节,不开启则为8个字节

  • ​基本数据类型:

3 对齐填充

对象的对齐填充不是必然存在的,也没有特别的含义,仅仅起到了占位符的作用。
由于HotSpot虚拟机的自动内存管理系统要求任何对象的大小都必须是8字节的整数倍。对象头部分正好是8字节的整数倍,如果当实例数据部分不是8字节整数倍时就需要对齐填充占位符来补全。

假如这个对象前面几项占用的字节为15则会补1个字节,则这个对象占用16字节

4 实验——观察对象的大小

使用技术:java Agent
JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。

JavaAgent 是运行在 main方法之前的拦截器,它内定的方法名叫 premain,也就是说先执行 premain 方法然后再执行 main 方法。
class文件在loading到内存中的时候,可以通过javaAgent做代理,抓取class的二进制字节码进行操作。

1.新建项目ObjectSize

  1. 新建项目ObjectSize (1.8)

  2. 创建文件ObjectSizeAgent

      package com.ls.jvm.agent;
      
      import java.lang.instrument.Instrumentation;
      
      /**
       * Created by 刘绍 on 2020/2/16.
       */
      public class ObjectSizeAgent {
          private static Instrumentation inst;
      
          public static void premain(String agentArgs, Instrumentation _inst) {
              inst = _inst;
          }
      
          public static long sizeOf(Object o) {
              return inst.getObjectSize(o);
          }
      }
      
    
  3. src目录下创建META-INF/MANIFEST.MF

      Manifest-Version: 1.0
      Created-By: mashibing.com
      Premain-Class: com.mashibing.jvm.agent.ObjectSizeAgent
    

    注意Premain-Class这行必须是新的一行(回车 + 换行),确认idea不能有任何错误提示

  4. 打包jar文件

  5. 在需要使用该Agent Jar的项目中引入该Jar包 project structure - project settings - library 添加该jar包

  6. 运行时需要该Agent Jar的类,加入参数:
    -javaagent:C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar

  7. 如何使用该类:

     import com.ls.jvm.agent.ObjectSizeAgent;
     
     public class SizeOfAnObject {
         
         
         public static void main(String[] args) {
             System.out.println(ObjectSizeAgent.sizeOf(new Object()));//16
             
             /*对象头8个字节+类指针4个字节,由于默认开启了-XX:+UseComparessedClassPointers 
             所以是4个字节,成员变量没有所以对象字节为8+4=12 最后Padding对齐,
             最后结果为16字节*/
             
             System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));//16
             /*对象头8个字节+类指针4个字节,由于默认开启了-XX:+UseComparessedClassPointers 
             所以是4个字节,数组长度4个字节,成员变量没有所以对象字节为8+4+4=16。
             结果是8的倍数所以不需要最后Padding对齐,最后结果为16字节*/
             
            	/*在执行main方法的时候加入参数 -XX:-UseComparessedClassPointers 关闭类指针压缩
            	ObjectSizeAgent.sizeOf(new Object()) 为16字节
            	对象头8个字节+类指针8个字节=16字节 
            	
            	ObjectSizeAgent.sizeOf(new int[] {})
             对象头8个字节+类指针8个字节+数组长度4个字节+Padding对齐4个字节 = 24字节*/
             
             System.out.println(ObjectSizeAgent.sizeOf(new P()));//32
             
             //对象头8个字节+类指针4个字节+4+4+4+1+1+4+1+Padding对齐1个字节 = 24字节
           
         }
     
     	private static class P {
             //8 _markword
             //4 _oop指针
             
             //-XX:+UseCOmparessedOops 成员变量类型引用指针。默认开启4字节,不开启为8字节
             int id;         //4
             String name;    //4
             int age;        //4
     
             byte b1;        //1
             byte b2;        //1
     
             Object o;       //4
             byte b3;        //1
     
         }
     }
    

参考:《深入理解Java虚拟机》第3版 周志明著

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值