首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java虚拟机--你的对象有多大如何计算对象大小

如何计算对象大小

上文中,笔者提到了对象头,并且说到了对象头中的Mark Word在32位的机器中会占用4字节,在64位机器中占用8字节。那么,整个对象会占用多大内存呢?

带着这样的疑问,我们来实际的测量下,一个对象到底会占用多大内存?

在实际计算之前,我们先来普及下接口Instrumentation和其实现类InstrumentationImpl。

Instrumentation介绍:

java.lang.instrument.Instrumentation接口:它提供了丰富的对结构的等各方面的跟踪和对象大小的测量的API。

sun.instrument.IntrumentationImpl类:sun开头的,Instrumentation接口的实现类,构造方法为private,没有任何getInstance的方法。

使用Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在JVM上的程序。开发者就可以实现更为灵活的运行时虚拟机监控,这样的特性实际上提供了一种虚拟机级别支持的AOP实现方式,使得开发者无需对JDK做任何升级和改动,就可以实现某些AOP的功能了。

说的直白点,Instrumentation就是一个代理。在代码层面,java.lang.instrument.Instrumentation是接口,sun.instrument.InstrumentationImpl是其实现类。

说了这么多,那么Instrumentation应该怎么使用呢?

值得一体的是,Instrumentation不能像我们平常new对象的方式来实现使用,代码层面我们无法得到Instrumentation的实例对象。开发者需要提供premain函数,让虚拟机注入。此外,premain函数在 main函数运行前执行。简要说来就是如下几个步骤:

(1)编写premain函数

代码语言:javascript
复制
编写一个 Java 类,包含如下两个方法当中的任何一个
1. public static void premain(String agentArgs, Instrumentation inst); 
2. public static void premain(String agentArgs); 
其中,1 的优先级比 2 高,将会被优先执行(1 和 2 同时存在时,2 被忽略)。

在这个premain函数中,开发者可以进行对类的各种操作。inst是java.lang.instrument.Instrumentation 的实例,由JVM自动传入。

java.lang.instrument.Instrumentation是instrument包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和类的操作等。

(2)class文件打成jar包

将这个Java类编译成class文件,再打成一个jar包,并在jar包中META-INF/MANIFEST.MF文件加入“ Premain-Class”来指定步骤1中编写的那个带有premain的Java类。

代码语言:javascript
复制
Premain-Class: java类的全限定类名

(3)运行

用命令行中输入如下命令:

代码语言:javascript
复制
java -javaagent:xxx.jar 被代理的类

说完了Instrumentation,接下来就用它来实际测量下对象的大小:

普通对象:

Instrumentation注入类:

代码语言:javascript
复制
public class ObjectSize {

    private static Instrumentation inst;

    public static void premain(String agentArgs, Instrumentation instP){
        inst = instP;
    }

    public static long sizeOf(Object obj){
        return inst.getObjectSize(obj);
    }
}

java.lang.instrument.Instrumentation.getObjectSize()的方式,这种方法得到的是Shallow Size,即遇到引用时,只计算引用的长度,不计算所引用的对象的实际大小。如果要计算所引用对象的实际大小,可以通过递归的方式去计算。

编写测试类:

代码语言:javascript
复制
public class JVMTest4 {

    private static class ObjectA {
        String str;  
        int i1; 
        byte b1; 
        byte b2; 
        int i2;  
        byte b3;  
    }

    public static void main(String[] args){
        System.out.println(ObjectSize.sizeOf(new ObjectA()));
    }
}

运行程序:

[图片上传失败...(image-c1c0a4-1525935829095)]

将打包好的jar文件,用解压缩工具打开,修改META-INF/MANIFEST.MF文件,告诉虚拟机在程序执行的时候执行ObjectSize类的permain方法,从名称也可以看出,含义为:“在main方法之前执行”。

编译运行

此步骤,是实际的运行过程,需要将上面的2个类进行编译,并且将ObjectSize打包,执行“java -javaagent:ObjectSize.jar JVMTest4”命令。

从截图中,我们可以看出ObjectA对象在内存中占用了32个字节。

上文中说了。对象的大小为8的倍数,如果不足8的倍数则会进行对齐填充。

下面,我们来手动计算下(64位机器,默认开启指针压缩)

原生类型

占用内存大小(字节)

boolean

1

byte

1

short

2

char

2

int

4

float

4

long

8

double

8

reference

开启指针压缩4、关闭指针压缩8

对象引用(reference)类型在64位机器上,关闭指针压缩时占用8字节, 开启时占用4字节。

实例数据:str(4)+i1(4)+b1(1)+b2(1)+i2(4)+b3(1) = 15字节

对象头:8(Mark Word) + 4(类型指针) = 12字节

对齐填充:5字节

总计:15 + 12 + 5 = 32字节

关闭指针压缩情况下:使用-XX:-UseCompressedOops命令

实例数据:str(8)+i1(4)+b1(1)+b2(1)+i2(4)+b3(1) = 19字节

对象头:8(Mark Word) + 8(类型指针) = 16字节

对齐填充:5字节

总计:19 + 16 + 5 = 40字节

数组对象:

Instrumentation注入类:

代码语言:javascript
复制
public class ObjectSize {

    private static Instrumentation inst;

    public static void premain(String agentArgs, Instrumentation instP){
        inst = instP;
    }

    public static long sizeOf(Object obj){
        return inst.getObjectSize(obj);
    }
}

编写测试类:

代码语言:javascript
复制
public class JVMTest4 {

    private static class ObjectA {
        String str;  
        int i1; 
        byte b1; 
        byte b2; 
    }

    private static class ObjectB {
        
    }

    public static void main(String[] args){
        System.out.println(ObjectSize.sizeOf(new ObjectA[0]));
        System.out.println(ObjectSize.sizeOf(new ObjectA[1]));
        System.out.println(ObjectSize.sizeOf(new ObjectA[2]));

        System.out.println(ObjectSize.sizeOf(new ObjectB[0]));
        System.out.println(ObjectSize.sizeOf(new ObjectB[1]));
        System.out.println(ObjectSize.sizeOf(new ObjectB[2]));
    }
}

运行程序:

image

从测试结果来看,数组对象要比普通对象占用内存空间更大。值得注意的是,数组占用内存的大小并不会根据成员变量的增加而增大。无论是否存在成员变量,都不会影响数组对象占用内存的大小。

你可能还有个疑惑?例子中的数组只设置了长度,而没有实际赋值对象,如果向对应的角标下赋值,数组对象占用内存的大小会有变化吗?

答案:NO!!

数组对象占用内存大小公式:

代码语言:javascript
复制
Mark Word + 类型指针 + 数组长度 + 实例数据(数组长度*数组元数据大小) +补齐填充

数组与普通对象不同之处,在与其实例数据部分。对于普通对象来说,实例数据就是其内部的成员变量;而对于数组来说,实例数据就是其内部的一个个原生对象,而原生对象所占用内存大小在开启指针压缩情况下为4字节,关闭指针压缩情况下为8字节。

开启指针压缩:

代码语言:javascript
复制
mark word 8  + 类型指针 4 + 数组长度 4 + 0*4 + 补齐 0  = 16

mark word 8  + 类型指针 4 + 数组长度 4 + 1*4 + 补齐 4  = 24

mark word 8  + 类型指针 4 + 数组长度 4 + 2*4 + 补齐 0  = 24

mark word 8  + 类型指针 4 + 数组长度 4 + 3*4 + 补齐 0  = 32

未开启指针压缩:

代码语言:javascript
复制
mark word 8  + 类型指针 8 + 数组长度 4 + 0*8 +  + 补齐 0  = 24

mark word 8  + 类型指针 8 + 数组长度 4 + 1*8 + 补齐 4  = 32

mark word 8  + 类型指针 8 + 数组长度 4 + 2*8 + 补齐 4  = 40

mark word 8  + 类型指针 8 + 数组长度 4 + 3*8 +  + 补齐 4  = 56

再次强调下:

我们例子中调用的getObjectSize()方法得到的是Shallow Size,即遇到引用时,只计算引用的长度,不计算所引用对象的实际大小。如果要计算所引用对象的实际大小,可以通过递归的方式去计算。本文暂不介绍此方式,有兴趣的朋友可以去网上查阅相关资料。

下一篇
举报
领券