java对象的内存布局

1.java的内存布局

对象在内存中的存储可以分为三个区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。


java对象的内存布局_第1张图片

对象头

  1. Mark Word:包含一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。在32位系统占4字节,在64位系统中占8字节;
  • 32bit虚拟机中的Mark Word存储结构


    java对象的内存布局_第2张图片
  • 64bit虚拟机中的Mark Word存储结构


    java对象的内存布局_第3张图片
  1. 类型指针(Class Pointer)
  • 用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节
  1. 数组长度
  • 如果对象是个数组的话,对象头中就必须要有一块用于记录数组长度的数据,不然虚拟机无法确定数组的大小。32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项(开启指针压缩),该区域长度也将由64位压缩至32位。32位HotSpot VM是不支持UseCompressedOops参数的,只有64位HotSpot VM才支持。

实际数据
对象实际数据包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节(64位系统中是8个字节)。

Primitive Type Memory Required(bytes)
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8
ref 4 或8

对于reference类型来说,在32位系统上占用4bytes, 在64位系统上占用8bytes。

对象填充
Java对象占用空间是8字节对齐的,即所有Java对象占用bytes数必须是8的倍数。例如,一个包含两个属性的对象:int和byte,如果在32位系统中则占用需要4(mark word)+4(类型指针)+4(int)+1(byte)=13个字节,这时就需要加上大小为3字节的padding进行8字节对齐,最终占用大小为16个字节;如果在64位系统中未开启压缩指针(后续会介绍)的话占用8+8+4+1=21,加上大小为3字节的padding进行8字节对齐,最终占用大小为24个字节;如果在64位系统开启了压缩指针的话占用8+4+4+1=17,加上大小为7字节的padding进行8字节对齐,最终占用大小也为24个字节,但是这只是巧合,正好与未开启压缩指针相等。

2.验证对象的内存布局

添加maven依赖


       org.openjdk.jol
       jol-core
       0.9

写一个测试程序

package com.renlijia.multithread;

import org.openjdk.jol.info.ClassLayout;
/**
 * @Author: ding
 * @Date: 2020-06-15 23:12
 */
public class Mem_Obj_内存布局 {

    public static void main(String[] args) {

        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
}

执行后结果:

java.lang.Object 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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     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 object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 09 80 0d (10010000 00001001 10000000 00001101) (226494864)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对于锁的具体解释参照下文中的synchronized锁的介绍

3.压缩指针理解

安装jdk后在控制台执行:java -XX:+PrintCommandLineFlags -version可以打印出结果:
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
这里面有两个参数:UseCompressedClassPointers和UseCompressedOops是跟压缩指针相关,UseCompressedClassPointers开启后,对象布局里的类型指针会进行压缩,从8字节压缩到4个字节,这个参数只对类型指针进行压缩;UseCompressedOops是对普通的对象进行压缩,例如Object obj = new Object();会在内存中创建一个对象,并将指针指向它,这里面的指针是普通对象被引用的指针,开启后对此指针进行压缩,从8字节编程4个字节

压缩指针失效

  1. 在32位系统中是没有压缩指针的,不存在失效与否,指针都是4个字节;
  2. 64位系统开启了压缩指针后,指针占用的存储从8字节变成4字节,但是当系统内存大于32G后,压缩指针失效,因为内存布局是8字节对齐,所以使用指针时,32G的数据实际只有4G的指针指向,因为每个对象占用的都是8的倍数的字节数(8字节对齐),所以使用4G就可以表示出32G的数据量,但是超过32G就无法表示,压缩指针失效,指针都占用8个字节,这就是为什么当内存从<32G变成大于32G后,应用占用的内存增大的原因;
  3. 堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间

开启和取消压缩指针
-XX:+UseCompressedOops 开启对象的压缩指针
-XX:-UseCompressedOops 取消对象的压缩指针
-XX:+UseCompressedClassPointers 开启类型指针的压缩
-XX:-UseCompressedClassPointers 取消类型指针的压缩

在小于32G的内存系统中压缩指针是默认开启的,当取消压缩指针,重新执行 Mem_Obj_内存布局 程序结果:
-XX:-UseCompressedOops -XX:-UseCompressedClassPointers

java.lang.Object 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 2c c0 27 (00000000 00101100 11000000 00100111) (666905600)
     12     4        (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 09 63 03 (10010000 00001001 01100011 00000011) (56822160)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4        (object header)                           00 2c c0 27 (00000000 00101100 11000000 00100111) (666905600)
     12     4        (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

这个时候类型指针使用8个字节

参考文章:
https://www.jianshu.com/p/91e398d5d17c
https://zhuanlan.zhihu.com/p/50984945
https://blog.csdn.net/hongzb_2296/article/details/86505898
https://blog.csdn.net/goodmentc/article/details/45880351
https://blog.csdn.net/tongdanping/article/details/79647337

你可能感兴趣的:(java对象的内存布局)