Java 对象的内存布局

发布时间:2024年01月11日

目录

一. 前言

二. Java 对象的内存布局

三. Java 对象结构

3.1. 对象头

3.1.1. Mark Word

3.1.2. 类型指针(Class Metadata?Pointer)

3.1.3. 数组长度(Length)

3.2. 实例数据

3.3. 对齐填充(Padding)

四. JVM 之 指针压缩

4.1.?压缩指针的由来

4.2. 如何压缩指针

4.3.?如何进一步扩大寻址空间

4.4.?JVM 压缩指针参数

五. Java 对象大小计算

5.1. 对象大小计算公式

5.2.?对象分析

5.3.?非数组对象,开启指针压缩

5.4.?非数组对象,关闭指针压缩

5.5.?数组对象开启指针压缩

5.6.?数组对象关闭指针压缩

六. 总结


一. 前言

? ? 作为一名 Java 程序员,我们在日常工作中使用这门面向对象的编程语言时,做的最频繁的操作大概就是去创建一个个的对象了。对象的创建方式有很多,可以通过 new、Spring 管理 Bean、反射、clone、反序列化等不同方式来创建,但最终使用时对象都要被放到内存中,那么你知道在内存中的 Java 对象是由哪些部分组成以及是怎么存储的吗?这篇文章可带你深入了解 Java 对象的内存布局。

二. Java 对象的内存布局

? ? Java 对象的内存布局分为两种,普通对象和数组对象。

通过图中可以看出,数组对象只是在对象头里多了数组长度这一项,普通对象(非数组对象)没有这一项,也不分配内存空间。

三. Java 对象结构

在JVM中,对象在内存中的布局分为三块区域:对象头实例数据对齐补全

3.1. 对象头

对象头由三部分组成:

  1. Mark Word:存储自身的运行时数据,例如 HashCode、GC 年龄、锁相关信息等内容。
  2. Class Metadata Pointer:类型指针指向它的类元数据的指针。
  3. Length:记录数组长度。只有对象是数组的情况下,才有这部分数据,若对象不是数组,则没有这部分,不分配空间。

3.1.1. Mark Word

? ? 用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持久的锁、偏向线程的 ID 等,通过存储的内容得知对象头是锁机制和 GC 的重要基础。

32位和64位的Mark Word

32位虚拟机的 Mark Word 的字节分配:

  1. 无锁 ——?对象的 hashcode:25bit;存放对象分代年龄:4bit; 存放是否偏向锁的标志位:1bit; 存放锁标志位为01:2bit。
  2. 偏向锁?——?在偏向锁中划分更细。开辟 25bit 的空间,其中存放线程 ID:23bit;存放Epoch:2bit;存放对象分代年龄:4bit;存放是否偏向锁标志:1bit (0表示无锁,1表示偏向锁);锁的标志位为01:2bit。
  3. 轻量级锁?——?在轻量级锁中直接开辟 30bit 的空间存放指向栈中锁记录的指针,2bit 存放锁的标志位,其标志位为00。
  4. 重量级锁?——?在重量级锁中和轻量级锁一样,30bit 的空间用来存放指向重量级锁的指针,2bit 存放锁的标志位,其标志位为10。
  5. GC 标记?——?开辟 30bit 的内存空间却没有占用,2bit 空间存放锁标志位为11。其中无锁和偏向锁的锁标志位都是01,只是在前面的 1bit 区分了这是无锁状态还是偏向锁状态。

64位虚拟机的 Mark Word 的字节分配:

  1. 无锁?——?unused:25bit;对象的 hashcode:31bit;Cmc_free:1bit;存放对象分代年龄:4bit; 存放是否偏向锁的标志位:1bit; 存放锁标志位为01:2bit。
  2. 偏向锁?——?偏向线程 ID:54bit;存放Epoch:2bit;Cmc_free:1bit;存放对象分代年龄:4bit;存放是否偏向锁标识:1bit (0表示无锁,1表示偏向锁);锁的标志位为01:2bit。
  3. 轻量级锁?——?在轻量级锁中直接开辟 62bit 的空间存放指向栈中锁记录的指针,2bit 存放锁的标志位,其标志位为00。
  4. 重量级锁?——?在重量级锁中和轻量级锁一样,62bit 的空间用来存放指向重量级锁的指针,2bit 存放锁的标识位,为10。
  5. GC 标记?——?开辟 62bit 的内存空间却没有占用,2bit 空间存放锁标志位为11。其中无锁和偏向锁的锁标志位都是01,只是在前面的 1bit 区分了这是无锁状态还是偏向锁状态。

3.1.2. 类型指针(Class Metadata?Pointer)

? ? 类型指针指向类的元数据地址,JVM 通过这个指针确定对象是哪个类的实例。32位的 JVM 占32位,4个字节,64位的 JVM 占64位,8个字节,但是64位的 JVM 默认会开启指针压缩,压缩后也只占4字节。

? ? 如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的 JVM 将会比32位的JVM 多耗费50%的内存。所以会默认开启指针压缩(如不开启,类型指针将占用8字节),UseCompressedOops 是默认开启的,该参数表示开启指针压缩,会将原来64位的指针压缩为32位(即由原8字节压缩到4字节大小)。

-XX:+UseCompressedClassPointers //开启压缩类指针
-XX:-UseCompressedClassPointers //关闭压缩类指针

// 这个JVM参数依赖UseCompressedOops这个参数,UseCompressedOops开启,UseCompressedClassPointers默认开启,可手工关闭,
// UseCompressedOops关闭,UseCompressedClassPointers不管开启还是关闭都不生效即不压缩。

3.1.3. 数组长度(Length)

? ? 如果对象是普通对象(非数组对象),则没有这部分,不占用空间。如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着 JVM 架构的不同而不同:32位的JVM上,长度为32位(4字节);64位 JVM 则为64位(8字节)。64位 JVM 如果开启+UseCompressedOops 选项,该区域长度也将由64位压缩至32位。

3.2. 实例数据

普通对象:实例数据。

数组对象:数组中的实例数据。

实例数据存放的是非静态的属性,也包括父类的所有非静态属性(private 修饰的也在这里,不区分可见性修饰符),基本类型的属性存放的是具体的值,引用类型及数组类型存放的是引用指针。

实例数据不同的类型所占的空间不同:

数据类型占用空间
char2个字节
boolean1个字节
byte1个字节
short2个字节
int4个字节
long8个字节
float4个字节
double8个字节
对象引用

对象指针压缩默认是被开启的,占用4个字节,配置 JVM 参数

-XX:+UseCompressedOops 后,占用8个字节

64 位的 JVM 默认开启普通对象指针压缩 -XX:+UseCompressedOops (OOP 即 ordinary object pointer),由原8字节压缩到4字节大小。

3.3. 对齐填充(Padding)

? ? 用于补齐对象内存长度的。因为 JVM 要求 Java 代码的对象必须是 8bit 的倍数。如果一个对象用不到 8N 个字节则需要对其填充,以此来补齐对象头和实例数据占用内存之后剩余的空间大小。如果对象头和实例数据已经占满了 JVM 所分配的内存空间,那么就不用再进行对齐填充了。所有的对象分配的字节总 SIZE 需要是8的倍数,如果前面的对象头和实例数据占用的总 SIZE 不满足要求,则通过对齐数据来填满。

四. JVM 之 指针压缩

4.1.?压缩指针的由来

? ? 计算机操作系统分32位和64位,这里的位在计算机里是用0和1来表示的,用32个(或64个)二进制0和1的组合来表示内存地址。

? ? 以32位为例,在普通的内存中,对象的大小最小是以1字节来计算的,通过0和1的排列组合,能够表示寻址的内存空间最大就是2^{32}个,换算成内存空间就是2^{32} / 1024 / 1024 / 1024 = 4G,也就是说32位的操作系统最大能寻址的内存空间只有4G。

? ? 同理,64位的操作系统(查阅资料显示其实没有用到64位,最多只用到了48位,这个可自行查阅资料,反正肯定比32位大的多)2^{48}?/ 1024 / 1024 / 1024 / 1024 = 256TB,这样内存就足够大了,但是目前还没有厂商能生产出这么大的内存。

? ? 4G 对于现在的 Java 应用系统来说,内存已经算小的了,那我们就会想到使用64位的系统,这样内存就可以更大了,但是当我们准备将32位系统切换到64位系统,起初我们可能会期望系统性能会立马得到提升,但现实情况可能并不是这样的,为什么呢?

  1. 32位系统对象指针是4字节,64位系统对象指针是8字节(1位表示1bit,8个bit 表示1字节),这样64位系统中的对象引用占用的内存空间是32位系统中的两倍大小,因此间接的导致了在64位系统中更多的内存消耗以及更频繁的 GC 发生,GC 占用的 CPU 时间越多,那么我们的应用程序占用 CPU 的时间就越少,响应会变慢,吞吐量会降低。
  2. 对象的引用变大了,那么 CPU 可缓存的对象相对就少了,降低了 CPU 缓存命中率,增加了对内存的访问,CPU 对 CPU 缓存的访问速度可比对内存的访问速度快太多了,所以大量的对内存访问,会降低 CPU 的执行效率,增加了执行时间,从而影响性能。

既然32位系统内存不够,64位内存够但又影响性能,那有没有折中方案来解决这两个问题呢,于是聪明的 JVM 开发者想到了利用压缩指针,在64位的操作系统中利用32位的对象指针引用获得超过 4G 的内存寻址空间。

4.2. 如何压缩指针

? ? 由于在 JVM 里,对象都是以8字节对齐的(即对象的大小都是8的倍数),所以不管用32位还是64位的二进制表示,末尾3位始终都是0。既然 JVM 已经知道了这些对象的内存地址后三位始终是0,那么这些无意义的0就没必要在堆中继续存储。相反,我们可以利用存储0的这3位 bit 存储一些有意义的信息,这样我们就多出3位 bit 的寻址空间,也就是说如果我们继续使用32位来存储指针,只不过后三位原本用来存储0的 bit 现在被我们用来存放有意义的地址空间信息,当寻址的时候,JVM 将这32位的对象引用左移3位即可(后三位补0)。我们原本32位的内存寻址空间一下变成了35位,可寻址的内存空间变为 2^{35}?/ 1024 / 1024 / 1024 = 32G,也就是说在64位系统 JVM 的内存可扩大到 32G 了,基本上可满足大部分应用的使用了。

? ? 所以在64位系统下,通过压缩指针我们可以继续使用32位来处理(引用指针由8字节可降低到4字节),存储的时候右移3位,寻址的时候左移3位,如下图所示:

? ? 这样一来,JVM 虽然额外的执行了一些位运算但是极大的提高了寻址空间,并且将对象引用占用内存大小降低了一半,节省了大量空间,况且这些位运算对于 CPU 来说是非常容易且轻量的操作,可谓是一举两得。

4.3.?如何进一步扩大寻址空间

? ? 前边提到我们在 Java 虚拟机堆中对象起始地址均需要对齐至8的倍数,不过这个数值我们可以通过 JVM 参数 -XX:ObjectAlignmentInBytes 来改变(默认值为8)。当然这个数值的必须是2的次幂,数值范围需要在 8 - 256 之间。

? ? 正是因为对象地址对齐至8的倍数,才会多出3位 bit 让我们存储额外的地址信息,进而将 4G 的寻址空间提升至 32G。

? ? 同样的道理,如果我们将 ObjectAlignmentInBytes 的数值设置为16呢?

? ? 对象地址均对齐至16的倍数,那么就会多出4位 bit 让我们存储额外的地址信息。寻址空间变为 2^{36}?/ 1024 / 1024 / 1024 = 64G。

? ? 通过以上规律,我们就能知道,在64位系统中开启压缩指针的情况,寻址范围的计算公式:4G * ObjectAlignmentInBytes = 寻址范围。

? ? 但是并不建议大家贸然这样做,因为增大了 ObjectAlignmentInBytes 虽然能扩大寻址范围,但是这同时也可能增加了对象之间的字节填充,导致压缩指针没有达到原本节省空间的效果。

4.4.?JVM 压缩指针参数

可以通过以下命令查看 Java 命令默认的启动参数:

java -XX:+PrintCommandLineFlags -version

通过下面这个命令,可以看到所有JVM参数的默认值:

java -XX:+PrintFlagsFinal -version

关于压缩指针的两个参数:

  • UseCompressedClassPointers:压缩类指针(开启时类指针占4字节,关闭时类指针占8字节);
  • UseCompressedOops:压缩普通对象指针(开启时引用对象指针占4字节,关闭时引用对象指针占8字节)。

Oops 是 Ordinary object pointers 的缩写,这两个参数默认是开启的,即? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -XX:+UseCompressedClassPointers,-XX:+UseCompressedOops,也可手动设置,如下所示:

-XX:+UseCompressedClassPointers //开启压缩类指针
-XX:-UseCompressedClassPointers //关闭压缩类指针
-XX:+UseCompressedOops  //开启压缩普通对象指针
-XX:-UseCompressedOops  //关闭压缩普通对象指针

注:32位 HotSpot VM 是不支持 UseCompressedOops 参数的,只有64位 HotSpot VM 才支持。Oracle JDK 从6 update 23开始在64位系统上会默认开启压缩指针。

五. Java 对象大小计算

5.1. 对象大小计算公式

以下表格展示了对象中各部分所占空间大小,单位:字节。

类型所属部分占用空间大小(压缩开启)占用空间大小(压缩关闭)
Markwork对象头88
类型指针对象头48
数组长度对象头44
byte对象体11
boolean对象体11
short对象体22
char对象体22
int对象体44
float对象体44
long对象体88
double对象体88
对象引用指针对象体48
对齐填充对齐填充对象头+对象体是8的倍数?0 :8 -(对象头+对象体)% 8对象头+对象体是8的倍数?0 :8 -(对象头+对象体)% 8

计算公式:对象大小 = 对象头 + 对象体(对象是数组时,对象体的大小=引用指针占用空间大小 *对象个数) + 对齐填充。

64位操作系统 32G 内存以下,默认开启对象指针压缩,对象头是12字节,关闭指针压缩,对象头是16字节。内存超过 32G 时,则自动关闭指针压缩,对象头占16字节。

5.2.?对象分析

使用 JOL 工具分析 Java 对象大小:

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

常用类及方法:
查看对象内部信息:?ClassLayout.parseInstance(obj).toPrintable();
查看对象外部信息:GraphLayout.parseInstance(obj).toPrintable();
查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize();
查看类内部信息:ClassLayout.parseClass(Object.class).toPrintable()。

使用到的测试类:

@Setter
class Goods {
    private byte b;
    private char type;
    private short age;
    private int no;
    private float weight;
    private double price;
    private long id;
    private boolean flag;
    private String goodsName;
    private LocalDateTime produceTime;
    private String[] tags;
    public static String str;
    public static int temp;
}

5.3.?非数组对象,开启指针压缩

64位 JVM,堆内存小于 32G 的情况下,默认是开启指针压缩的。

public static void main(String[] args) {
    Goods goods = new Goods();
    goods.setAge((short) 10);
    goods.setNo(123456);
    goods.setId(111L);
    goods.setGoodsName("方便面");
    goods.setFlag(true);
    goods.setB((byte)1);
    goods.setPrice(1.5d);
    goods.setProduceTime(LocalDateTime.now());
    goods.setType('A');
    goods.setWeight(0.065f);
    goods.setTags(new String[] {"food", "convenience", "cheap"});
    Goods.str = "test";
    Goods.temp = 222;
    System.out.println(ClassLayout.parseInstance(goods).toPrintable());
}

先不看输出结果,按上面的公式计算一下对象的大小:

  • 对象头:8字节(Mark Word)+4字节(类指针)=12字节;
  • 对象体:1字节(属性 b)+ 2字节(属性 type)+ 2字节(属性 age)+ 4字节(属性 no)+ 4字节(属性 weight)+ 8字节(属性 price)+ 8字节(属性 id)+ 1字节(属性 flag) + 4字节(属性 goodsName 指针) + 4字节(属性 produceTime 指针) + 4字节(属性 tags 指针)= 42字节(注意:静态属性不参与对象大小计算);
  • 对齐填充:8 -(对象头+对象体)% 8 = 8 - (12 + 42) % 8 = 2字节;
  • 对象大小 = 对象头 + 对象体 + 对齐填充 = 12字节 + 42字节 + 2字节 = 56字节。

执行看运行结果:

com.star95.study.jvm.Goods object internals:
OFF  SZ                      TYPE DESCRIPTION               VALUE
  0   8                           (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                           (object header: class)    0x2000c043
 12   4                       int Goods.no                  123456
 16   8                    double Goods.price               1.5
 24   8                      long Goods.id                  111
 32   4                     float Goods.weight              0.065
 36   2                      char Goods.type                A
 38   2                     short Goods.age                 10
 40   1                      byte Goods.b                   1
 41   1                   boolean Goods.flag                true
 42   2                           (alignment/padding gap)   
 44   4          java.lang.String Goods.goodsName           (object)
 48   4   java.time.LocalDateTime Goods.produceTime         (object)
 52   4        java.lang.String[] Goods.tags                [(object), (object), (object)]
Instance size: 56 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total

这里有一个特殊的地方,打印输出的属性顺序跟代码里的顺序不一致,这是因为 JVM 进行优化,也就是指令重排序,会根据属性类型的大小、执行的先后顺序对结果是否有影响、最小填充大小等因素计算出对象最小应占用的空间。

5.4.?非数组对象,关闭指针压缩

关闭压缩指针,类指针和引用对象指针都占8字节,推算一下对象大小:

  • 对象头:8字节(Mark Word)+ 8字节(类指针)= 16字节;
  • 对象体:1字节(属性 b)+ 2字节(属性 type)+ 2字节(属性 age)+ 4字节(属性 no)+ 4字节(属性 weight)+ 8字节(属性 price)+ 8字节(属性 id)+ 1字节(属性 flag) + 8字节(属性 goodsName 指针) + 8字节(属性 produceTime 指针) + 8字节(属性 tags 指针)= 54字节(注意:静态属性不参与对象大小计算);
  • 对齐填充:8 -(对象头+对象体)% 8 = 8 - (16 + 54) % 8 = 2字节;
  • 对象大小 = 对象头 + 对象体 + 对齐填充 = 16字节 + 54字节 + 2字节 = 72字节。

运行时增加JVM参数如下:

-XX:-UseCompressedClassPointers -XX:-UseCompressedOops

public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("方便面");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        System.out.println(ClassLayout.parseInstance(goods).toPrintable());
    }
}

执行看运行结果:

com.star95.study.jvm.Goods object internals:
OFF  SZ                      TYPE DESCRIPTION               VALUE
  0   8                           (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8                           (object header: class)    0x00000000175647b8
 16   8                    double Goods.price               1.5
 24   8                      long Goods.id                  111
 32   4                       int Goods.no                  123456
 36   4                     float Goods.weight              0.065
 40   2                      char Goods.type                A
 42   2                     short Goods.age                 10
 44   1                      byte Goods.b                   1
 45   1                   boolean Goods.flag                true
 46   2                           (alignment/padding gap)   
 48   8          java.lang.String Goods.goodsName           (object)
 56   8   java.time.LocalDateTime Goods.produceTime         (object)
 64   8        java.lang.String[] Goods.tags                [(object), (object), (object)]
Instance size: 72 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total

5.5.?数组对象开启指针压缩

默认是开启压缩指针的,类指针和引用对象指针都占4字节,推算一下对象大小:

  • 对象头:8字节(Mark Word)+ 4字节(类指针) + 4字节(数组长度)= 16字节;
  • 对象体:4字节 * 3 = 12字节;
  • 对齐填充:8 -(对象头+对象体)% 8 = 8 - (16字节 + 12字节)% 8 = 4字节;
  • 对象大小 = 对象头 + 对象体 + 对齐填充 = 16字节 + 12字节 + 4字节 = 32字节。
public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("方便面");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        Goods[] goodsArr = new Goods[3];
        goodsArr[0] = goods;
        System.out.println(ClassLayout.parseInstance(goodsArr).toPrintable());
    }
}

?执行看运行结果:

[Lcom.star95.study.jvm.Goods; object internals:
OFF  SZ                         TYPE DESCRIPTION               VALUE
  0   8                              (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                              (object header: class)    0x2000c18d
 12   4                              (array length)            3
 16  12   com.star95.study.jvm.Goods Goods;.<elements>         N/A
 28   4                              (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

5.6.?数组对象关闭指针压缩

关闭压缩指针,类指针和引用对象指针都占8字节,推算一下对象大小:

  • 对象头:8字节(Markword)+8字节(类指针) + 4字节(数组长度)= 20字节;
  • 对象体:8字节 * 3 = 24字节;
  • 对齐填充:8 -(对象头+对象体)% 8 = 8 - (20+ 24) % 8 = 4字节;
  • 对象大小 = 对象头 + 对象体 + 对齐填充 = 20字节 + 24字节 + 4字节 = 48字节。

运行时增加 JVM 参数如下:

-XX:-UseCompressedClassPointers -XX:-UseCompressedOops
public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("方便面");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        Goods[] goodsArr = new Goods[3];
        goodsArr[0] = goods;
        System.out.println(ClassLayout.parseInstance(goodsArr).toPrintable());
    }
}

执行看运行结果:

[Lcom.star95.study.jvm.Goods; object internals:
OFF  SZ                         TYPE DESCRIPTION               VALUE
  0   8                              (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8                              (object header: class)    0x0000000017e04d70
 16   4                              (array length)            3
 20   4                              (alignment/padding gap)   
 24  24   com.star95.study.jvm.Goods Goods;.<elements>         N/A
Instance size: 48 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

通过以上对象分析,我们看到在开启压缩指针的情况下,对象的大小会小很多,节省了内存空间。

六. 总结

? ? 通过以上的分析,基本已经把 Java 对象的结构讲清楚了,另外对象占用内存空间大小也计算出来了,有助于进行 JVM 调优分析,64位的虚拟机内存在 32G 以下时默认是开启压缩指针的,超过32G 自动关闭压缩指针,主要目的都是为了提高寻址效率。

? ? 另外,本文是通过 JOL 工具计算对象占用空间的大小,不包括引用对象实际占用的内存大小,因为计算时是按引用对象的指针占用空间大小计算的,可能跟其他工具计算的结果不一样,具体跟工具的计算逻辑有关,比如跟 JDK 自带的 jvisualvm 工具通过堆 dump 出来看到的对象大小不一样,感兴趣的可自行验证。

文章来源:https://blog.csdn.net/mrluo735/article/details/135356102
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。