《EffectiveJava》之JVM中对象内存分配

1.对象创建

Java运行程序过程中创建对象——通过new关键字实现。虚拟机遇到一条new指令时,首先在常量池中定位类的符号引用,检查这个符号引用代表的类是否已经被加载,解析和初始化。如果没有则执行类加载机制。

类加载完成后,虚拟机将new 出的对象分配内存, 从Java堆中划分出一块确定大小的内存。

2. 对象内存分布

这个在无数面试题中被提到了很多次,但都往往只是留于书面形式的理解,这次我使用OpenJDK提供的Java虚拟机对象布局分析工具:JOL 来实操一遍。

首先添加maven依赖:

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

接着分别对Integer,String,list类型进行测试,看在Java对象究竟是怎样在Java堆中进行内存分配的。

 @Test
  public void integerTest() {
    Integer integer = 200;

    //对象内部布局
    System.out.println(ClassLayout.parseInstance(integer).toPrintable());
    System.out.println("---------------------");

    //对象的引用图
    System.out.println(GraphLayout.parseInstance(integer).toPrintable());
  }

Integer类型对象内存分配情况

java.lang.Integer object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           be 22 00 20 (10111110 00100010 00000000 00100000) (536879806)
     12     4    int Integer.value                             200
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

---------------------
java.lang.Integer@39c0f4ad object externals:
          ADDRESS       SIZE TYPE              PATH                           VALUE
        6b820e7c0         16 java.lang.Integer                                200

Addresses are stable after 1 tries.

可以看到内部编码 object header 对象头为12字节,实例数据占4字节, Instance size为16字节。

String类型内存占用情况

  @Test
  public void stringTest() {
    String str = "demo test string";

    //对象内部布局
    System.out.println(ClassLayout.parseInstance(str).toPrintable());
    System.out.println("---------------------");

    //对象的引用图
    System.out.println(GraphLayout.parseInstance(str).toPrintable());
  }

采用四个字符组成的String字符串

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)                           da 02 00 20 (11011010 00000010 00000000 00100000) (536871642)
     12     4   char[] String.value                              [d, e, m, o]
     16     4      int String.hash                               0
     20     4          (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

---------------------
java.lang.String@3c09711bd object externals:
          ADDRESS       SIZE TYPE             PATH                           VALUE
         d6feb330         24 java.lang.String                                (object)
         d6feb348         24 [C.value                         [d, e, m, o]

可以看出内部编码字符串对象占 24 个字节,其中头部占 12 个字节,实例数据占 8 个字节(value 字符数组 4 个字节,int 类型 hash 属性占 4 个字节),另外有 4 个字节的对齐填充(loss due to the next object alignment),共计 24 个字节。

外部编码有String的24字节和String 中内容默认的24个字节

List集合的内存占用情况

  @Test
  public void listTest() {
    List<String> list = new ArrayList<>();

    for (int i = 0; i < 3; i++) {
      list.add(String.valueOf(i));
    }
    //对象内部布局
    System.out.println(ClassLayout.parseInstance(list).toPrintable());
    System.out.println("---------------------");

    //对象的引用图
    System.out.println(GraphLayout.parseInstance(list).toPrintable());

    //总字节数
    System.out.println(GraphLayout.parseInstance(list).totalSize());
  }

这里添加三个String类型的值到集合中

java.util.ArrayList 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)                           72f 00 20 (01111110 00101111 00000000 00100000) (536883070)
     12     4                  int AbstractList.modCount                     3
     16     4                  int ArrayList.size                            3
     20     4   java.lang.Object[] ArrayList.elementData                     [(object), (object), (object), nullnullnullnullnullnullnull]
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

---------------------
java.util.ArrayList@4590c9c3d object externals:
          ADDRESS       SIZE TYPE                PATH                           VALUE
         d6feb380         24 java.util.ArrayList                                (object)
         d6feb398         24 [C                  .elementData[0].value          [0]
         d6feb3b0         24 java.lang.String    .elementData[0]                (object)
         d6feb3c8         56 [Ljava.lang.Object; .elementData                   [(object), (object), (object), nullnullnullnullnullnullnull]
         d6feb400         24 [C                  .elementData[1].value          [1]
         d6feb418         24 java.lang.String    .elementData[1]                (object)
         d6feb430         24 [C                  .elementData[2].value          [2]
         d6feb448         24 java.lang.String    .elementData[2]                (object)

         224

这里添加的是String类型的数字,其中ArrayList内部编码占24个字节,这个是固定不变的。

外部编码有 24个字节的ArrayList,每个集合序列中的0, 1, 2 和其value值分别占24个字节,再加上56个字节存储ArrayList, 其中每一个Object占10个字节,每个null占2个字节。(不满8个字节进行自动补全)

单独一个Object对象占用情况

@Test
  public void listObject() {
    Object object = new Object();
    //对象内部布局
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
    System.out.println("---------------------");

    //对象的引用图
    System.out.println(GraphLayout.parseInstance(object).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)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

---------------------
java.lang.Object@337d0578d object externals:
          ADDRESS       SIZE TYPE             PATH                           VALUE
         d6fed190         16 java.lang.Object                                (object)

一目了然,一个单纯的Object对象只占用3个对象头的12个字节 + 补全的4个字节 = 16个字节。

自定义对象占用内存

 @Test
  public void listObjectByOwn() {
    ActivityChannel activityChannel = new ActivityChannel();
    //对象内部布局
    System.out.println(ClassLayout.parseInstance(activityChannel).toPrintable());
    System.out.println("---------------------");

    //对象的引用图
    System.out.println(GraphLayout.parseInstance(activityChannel).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)                           73 32 01 20 (01110011 00110010 00000001 00100000) (536949363)
     12     4      java.lang.Long ActivityChannel.id                        null
     16     4      java.lang.Long ActivityChannel.activityInfoId            null
     20     4    java.lang.String ActivityChannel.qrCode                    null
     24     4    java.lang.String ActivityChannel.shortUrl                  null
     28     4   java.lang.Integer ActivityChannel.channel                   null
     32     4    java.lang.String ActivityChannel.shareToken                null
     36     4                     (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

---------------------
com.example.test.bean.ActivityChannel@5cc7c2a6d object externals:
          ADDRESS       SIZE TYPE                                  PATH                           VALUE
         d6ff3ea0         40 com.example.test.bean.ActivityChannel                                (object)

一个自定义的ActivityChannel对象,在不进行赋值的情况下占用内存就是内部编码所占用的内存,即每一个字段占4个字节。

终极:集合中添加自定义对象并进行存储所占内存

 @Test
  public void listTest() {
    ActivityChannel activityChannel = new ActivityChannel(100L,200L,"faf""fsgs"4"4314134");
    List<ActivityChannel> list = new ArrayList<>(5);
    list.add(activityChannel);

    ActivityChannel activityChannel2 = new ActivityChannel(100L,200L,"faf""fsgs"4"3141512");
    list.add(activityChannel2);

    ActivityChannel activityChannel3 = new ActivityChannel(200L,300L,"faf""fsgs"4"3141512");
    list.add(activityChannel3);
    //对象内部布局
    System.out.println(ClassLayout.parseInstance(list).toPrintable());
    System.out.println("---------------------");

    //对象的引用图
    System.out.println(GraphLayout.parseInstance(list).toPrintable());

    //总字节数
    System.out.println(GraphLayout.parseInstance(list).totalSize());
  }
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)                           72f 00 20 (01111110 00101111 00000000 00100000) (536883070)
     12     4                  int AbstractList.modCount                     3
     16     4                  int ArrayList.size                            3
     20     4   java.lang.Object[] ArrayList.elementData                     [(object), (object), (object), nullnull]
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

---------------------
java.util.ArrayList@6574b225d object externals:
          ADDRESS       SIZE TYPE                                  PATH                           VALUE
         d6184d40         16 java.lang.Integer                     .elementData[2].channel        4
         d6184d50    5948928 (something else)                      (somewhere else)               (something else)
         d6731350         24 java.lang.Long                        .elementData[1].id             100
         d6731368    9561336 (something else)                      (somewhere else)               (something else)
         d704f860         40 com.example.test.bean.ActivityChannel .elementData[0]                (object)
         d704f888         24 java.lang.Long                        .elementData[0].activityInfoId 200
         d704f8a0         24 java.lang.String                      .elementData[2].qrCode         (object)
         d704f8b8         24 [C                                    .elementData[2].qrCode.value   [f, a, f]
         d704f8d0         24 java.lang.String                      .elementData[2].shortUrl       (object)
         d704f8e8         24 [C                                    .elementData[2].shortUrl.value [f, s, g, s]
         d704f900         24 java.lang.String                      .elementData[0].shareToken     (object)
         d704f918         32 [C                                    .elementData[0].shareToken.value [4314134]
         d704f938         24 java.util.ArrayList                                                  (object)
         d704f950         40 [Ljava.lang.Object;                   .elementData                   [(object), (object), (object), nullnull]
         d704f978         40 com.example.test.bean.ActivityChannel .elementData[1]                (object)
         d704f9a0         24 java.lang.Long                        .elementData[1].activityInfoId 200
         d704f9b8         24 java.lang.String                      .elementData[2].shareToken     (object)
         d704f9d0         32 [C                                    .elementData[2].shareToken.value [3141512]
         d704f9f0         40 com.example.test.bean.ActivityChannel .elementData[2]                (object)
         d704fa18         24 java.lang.Long                        .elementData[2].id             200
         d704fa30         24 java.lang.Long                        .elementData[2].activityInfoId 300

Addresses are stable after 1 tries.


528

集合中添加自定义对象会在集合中对于相同值进行覆盖,这样内存中只需要存储集合序列中不同的值即可,更进一步减少内存的占用。

添加3个ActivityChannel对象的list集合只用了528个字节。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值