JVM对象对内存的影响

一、对象的内存布局

我们平时在开发中,评估内存使用的时候,很容易只算数据本身的占用内存,而忽略了对象头的内存,但是在很多情况下,对象头的内存甚至可能会超过数据本身,所以这块数据不可忽略。

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

在这里插入图片描述

1.1 HotSpot虚拟机对象的对象头

  • 第一类是用于存储对象自身的运行时数据,如哈希码,GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别占用4个字节和8个字节(未开启压缩指针)

  • 第二类是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。在开启指针压缩的状况下占 4 字节,未开启状况下占 8 字节。(64位操作)

  • 如果是数组,那在对象头中还必须有一块用于记录数组长度的数据,占4个字节

1.2 对齐填充

对齐填充仅仅起着占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍)(书里是这样写的,但是实际测试发现并不是,也可能是12字节),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

那么为何非要进行 8 字节对齐呢?这样岂不是浪费了空间资源?

因为CPU访问内存还是一个较慢的操作,所以计算机并非逐个字节读取,而是以2、4、8的倍数字节块读取内存,它们会要求这些数据的首地址的值是某个数是k(通常是4或8)的倍数 ,这就是所谓的内存对齐。大白话就是,各种数据类型都要一定的规则进行排列,而不是一个接一个的排放,这就是对齐。对齐是在效率和空间上做的权衡。

如果不对齐,在读取某个对象的时候,就会需要多读不属于它的部分,如下图,如果没有做内存对齐,因为CPU只能读取4或8的倍数,在每次读取Object2的时候,都必须把Object1和Object3都读进来
在这里插入图片描述

二、在实际操作中看一个对象有多大

首先引入maven依赖

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

2.1 Integer 占多大

import org.openjdk.jol.info.ClassLayout;
/**
 * @Author: ZhaoLei
 * @Create date: 2022/12/28
 * @Description:
 */

public class TestInteger {
    public static void main(String[] args) {
        Integer i1 = 1;

        ClassLayout classLayout_i1 = ClassLayout.parseInstance(i1);
        System.out.println(classLayout_i1.toPrintable());
    }
}
java.lang.Integer 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)                           bf 22 00 f8 (10111111 00100010 00000000 11111000) (-134208833)
     12     4    int Integer.value                             1
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

可以看到Integer,刚好是12字节的对象头 + 4字节的实例数据,占用16字节

2.2 Short 占多大

我们都知道,基本数据类型short,在内存中是16位,也就是2字节存储,但是它被包装成Short后,会占用多大呢?

import org.openjdk.jol.info.ClassLayout;

/**
 * @Author: ZhaoLei
 * @Create date: 2022/12/28
 * @Description:
 */

public class TestShort {
    public static void main(String[] args) {
        Short s1 = 1;

        ClassLayout classLayout_s1 = ClassLayout.parseInstance(s1);
        System.out.println(classLayout_s1.toPrintable());
    }
}
java.lang.Short 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)                           78 22 00 f8 (01111000 00100010 00000000 11111000) (-134208904)
     12     2   short Short.value                               1
     14     2         (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

可以看到,short被包装后,占用了16个字节,其中有2个字节的对齐填充。

看了上面两个示例,我们发现一个包装类会比基本数据类型占的空间大的多,对象头 + 对齐填充比实际数据占的空间都要多。所以能用基本数据类型的情况下,就不用包装类!!!

2.3 String 占多大

import org.openjdk.jol.info.ClassLayout;

/**
 * @Author: ZhaoLei
 * @Create date: 2022/12/28
 * @Description:
 */

public class TestString {
    public static void main(String[] args) {
        String str = "abc";

        ClassLayout classLayout_str = ClassLayout.parseInstance(str);
        System.out.println(classLayout_str.toPrintable());
    }
}
java.lang.String 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)                           da 02 00 f8 (11011010 00000010 00000000 11111000) (-134216998)
     12     4   char[] String.value                              [a, b, c]
     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

可以看到String对象本身占了24个字节,但是String这里的实例数据是指向了一个char型数组,所以我们还需要加上char型数组所占的空间

import org.openjdk.jol.info.ClassLayout;

/**
 * @Author: ZhaoLei
 * @Create date: 2022/12/28
 * @Description:
 */

public class TestCharArray {
    public static void main(String[] args) {
        char[] chars = new char[] {'a', 'b', 'c'};

        ClassLayout classLayout_chars = ClassLayout.parseInstance(chars);
        System.out.println(classLayout_chars.toPrintable());
    }

}
[C 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)                           41 00 00 f8 (01000001 00000000 00000000 11111000) (-134217663)
     12     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
     16     6   char [C.<elements>                             N/A
     22     2        (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

可以看到,char型数组占了24个字节,char数组的大小还要取决于元素个数

综上,String “abc” 会在内存中占用48个字节,这里是基于JDK 1.8测试的,String的底层是char型数组,一个char占2个字节,如果是JDK 11及以上,String的底层实现是byte数组了,一个byte占一个字节,存储可能会不同

2.4 HashMap占多大

HashMap是一个集合,我们关注集合对象本身这个对象占的空间,其实意义不大,要在其中填充进去数以后,才有意义

准备工作:我们先把50w个cuid加载到HashMap,然后用 JvisualVM 的堆dump进行分析,JvisualVM一般在$JAVA_HOME的bin目录中可以找到,如果找不到,就直接下载一个

可以看到,50w个cuid加载到HashMap后,其占空间的主要是3个结构:String对象、char[]数组以及HashMap的Node

在这里插入图片描述

在HashMap的底层是Node数组加LinkedHashMap或者红黑树,可以看到,我们大部分用到的都是Node,说明发生Hash冲突的概率比较低。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值