Java 对象在内存

2022-06-20 19:59:24 浏览数 (1)

平常工作中,我们只new一个对象,却基本不关心这个对象到底占了我们多少空间.

今天就一起看下,对象的空间占用情况

首先,内存中的对象是由以下几部分构成的:

结合上图以下类为例,看下内存空间的占用情况

代码语言:javascript复制
public class MyObject {
    int i = 123;
    long l = 234;
    String str = "12345";
}

1. Mark word: 记录线程,锁等对象状态,64位机占用8字节;32位机占用4字节; 当前主机是64位占8字节

2. Klass pointer: 指向类元数据的指针,开启指针压缩时, 占4字节;

3. 数组长度: 如果是数组对象,存储数组长度,如果不是数组,没有该区域;

4. 头部数据补全: 只是在关闭指针压缩,并且是数组对象的情况下才会用到;

5. 实例属性: 存储实例属性; 例如: int变量4字节; long变量8字节; String对象是一个指针占4字节;

6. 对齐补全: JVM中开辟的内存空间必须是8字节的倍数, 如果缺少位数,需要补全为8的倍数; 以上各字段共28字节,需补全4字节

所以MyObject对象共占用8 4 0 16 4=32字节

下面利用openjdk的jol工具包验证下

代码语言:javascript复制
public static void main(String[] args) {
    MyObject object = new MyObject();
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
}

执行结果:

代码语言:javascript复制
MyObject 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)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int MyObject.i                                123
     16     8               long MyObject.l                                234
     24     4   java.lang.String MyObject.str                              (object)
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal   4 bytes external = 4 bytes total

和我们预期是一样,共占用32字节,有4字节的空间损失.

数组结构内存空间

再看下数组结构的内存空间分配

代码语言:javascript复制
public static void main(String[] args) {
    MyObject[] array = new MyObject[]{new MyObject(),new MyObject()};
    System.out.println(ClassLayout.parseInstance(array).toPrintable());
}

对象头部分除MarkWord和klass pointer以外, 还有4字节用来存储数组长度

代码语言:javascript复制
[LMyObject; 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)                           d1 c4 00 f8 (11010001 11000100 00000000 11111000) (-134167343)
     12     4            (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
     16     8   MyObject [LMyObject;.<elements>                    N/A
Instance size: 24 bytes
Space losses: 0 bytes internal   0 bytes external = 0 bytes total

其他类型的实例属性

再看看其他属性的内存占用情况是如何的.

代码语言:javascript复制
public class OtherObject {
    public static final float PI = 3.14f;
    boolean b = false;
    char c = 'a';
    short s = 20;
    int i = 123;
    long l = 234;
    float f = 0.1f;
    double d = 0.2d;
    String str = "12345";
    Map map = new HashMap();
    Set set = new HashSet();
    List list = new ArrayList();
    public static void main(String[] args) {
        OtherObject object = new OtherObject();
        object.map.put("a", "a");
        object.set.add("a");
        object.list.add("a");
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        System.out.println(ClassLayout.parseInstance(object).headerSize());
        System.out.println(ClassLayout.parseInstance(object).instanceSize());
    }
}

通过执行结果可以发现:

类变量是不用开辟内存空间的

boolean类型占用1个字节,后面需要3字节对齐补全;

short和char类型都需要2字节的空间, 但JVM进行了优化,并不需增加空间对齐补全;

在最后需要4字节对齐补全;

整个对象需要64字节,空间损失了7字节.

代码语言:javascript复制
OtherObject 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)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int OtherObject.i                             123
     16     8               long OtherObject.l                             234
     24     8             double OtherObject.d                             0.2
     32     4              float OtherObject.f                             0.1
     36     2               char OtherObject.c                             a
     38     2              short OtherObject.s                             20
     40     1            boolean OtherObject.b                             false
     41     3                    (alignment/padding gap)                  
     44     4   java.lang.String OtherObject.str                           (object)
     48     4      java.util.Map OtherObject.map                           (object)
     52     4      java.util.Set OtherObject.set                           (object)
     56     4     java.util.List OtherObject.list                          (object)
     60     4                    (loss due to the next object alignment)
Instance size: 64 bytes
Space losses: 3 bytes internal   4 bytes external = 7 bytes total

指针压缩

指针压缩是指在64位机上,也使用32位表示引用地址.

在上面的例子中都是开启指针压缩的,因为从jdk6之后就是默认开启的.

共涉及到2个JVM参数

-XX: UseCompressedOops

会使用4字节来表示java object的引;

默认开启

-XX: UseCompressedClassesPointers

默认开启

用4字节来表示进程中的class pointer;

UseCompressedClassPointers的开启是依赖于UseCompressedOops的开启.

同样是MyObject对象,在关闭指针压缩时,共需要40字节的空间

Mark down使用8字节

Klass pointer 使用8字节

str指针也占用8字节;

代码语言:javascript复制
MyObject 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)                           28 30 60 a2 (00101000 00110000 01100000 10100010) (-1570754520)
     12     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     16     8               long MyObject.l                                234
     24     4                int MyObject.i                                123
     28     4                    (alignment/padding gap)                  
     32     8   java.lang.String MyObject.str                              (object)
Instance size: 40 bytes
Space losses: 4 bytes internal   0 bytes external = 4 bytes total

指针压缩的好处

1. 节省内存空间

2. 提升执行效率

头部空间补全

在关闭指针压缩之后,在看数组对象的内存空间,就可以发现产生了数据补全的情况

Mark down使用8字节

Klass pointer 使用8字节

数组长度 使用4字节

头部数据补全 使用4字节

代码语言:javascript复制
[LMyObject; 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)                           00 3a c2 0c (00000000 00111010 11000010 00001100) (214055424)
     12     4            (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     16     4            (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
     20     4            (alignment/padding gap)                  
     24    16   MyObject [LMyObject;.<elements>                    N/A
Instance size: 40 bytes
Space losses: 4 bytes internal   0 bytes external = 4 bytes total

以上,就是一个对象内存的占用情况.

最后,附上openjdk.jol的maven依赖

代码语言:javascript复制
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <version>0.9</version>
    <artifactId>jol-cli</artifactId>
</dependency>

0 人点赞