java对象的内存布局

一、对象在内存中的存储布局

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

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

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


1、对象头-Mark Word

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

对象头结构:

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

以 32位虚拟机为例,来看一下其 Mark Word 的字节具体是如何分配的?

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

2、类型指针-Class Pointer

    Class Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

64 位的JVM默认开启-XX:+UseCompressedClassPointers压缩类型指针开关,由原8字节压缩到4字节大小

3、实例数据

  • 普通对象:实例数据
  • 数组:数组长度(4个字节)+  数组中的实例数据

疑惑点:很多文章都写数组长度是属于对象头的,但是如下数组实验,类型并没有标记object header

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

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

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


4、对齐填充字节- padding :

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

二、工具

1、jol工具

Idea 导入 pom依赖


    org.openjdk.jol
    jol-core
    0.16

jol常用方法:

1.使用jol计算对象的大小(单位为字节):ClassLayout.parseInstance(obj).instanceSize()
2.使用jol查看对象内部的内存布局:ClassLayout.parseInstance(obj).toPrintable()
3.查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize()

三、实验

1、对象 和 数组

(1)对象类型

代码:

public class markword01_EmptyProperties {
    public static void main(String[] args) {
        System.out.println("空属性-对象布局================================");
        markword01_EmptyProperties entity = new markword01_EmptyProperties();
        // 打印java 对象内存布局
        System.out.println(ClassLayout.parseInstance(entity).toPrintable());
    }
}

输出结果:

com.tsj.threads.markword01_EmptyProperties object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf800c105
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结果分析:

- OFF(OFFSET):偏移地址,单位字节; 
 - SZ(SIZE):占用的内存大小,单位为字节; 
 - TYPE DESCRIPTION:类型描述。
  		object header为对象头,object header 包含 mark + class;
  		gap字段,表示补齐padding。 
- VALUE:对应内存中当前存储的值; 
- Instance size:实例字节数值大小,
  		即:一个空的java对象(不包含任意字段属性)实例,其实例大小为16Byte。
  		怎么计算?mark + class + gap = 16. 
-  Space losses: 0 bytes internal + 4 bytes external
   = 4 bytes total:表示内存补齐Padding,占用4个字节

(2)数组

代码:

public class markword01_EmptyArrayEntity {
    public static void main(String[] args) {
        System.out.println("数组布局================================");
        markword01_EmptyArrayEntity[] entity = new markword01_EmptyArrayEntity[4];
        // 打印java 对象内存布局
        System.out.println(ClassLayout.parseInstance(entity).toPrintable());
    }
}

输出结果:

[Lcom.tsj.threads.markword01_EmptyArrayEntity; object internals:
OFF  SZ                                          TYPE DESCRIPTION                               VALUE
  0   8                                               (object header: mark)                     0x0000000000000001 (non-biasable; age: 0)
  8   4                                               (object header: class)                    0xf800c143
 12   4                                               (array length)                            4
 12   4                                               (alignment/padding gap)                   
 16  16   com.tsj.threads.markword01_EmptyArrayEntity markword01_EmptyArrayEntity;.   N/A
Instance size: 32 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

结果分析:

- OFF:偏移地址,单位字节; 
 - SZ:占用的内存大小,单位为字节; 
 - TYPE DESCRIPTION:类型描述。
	   object header为对象头,object header 包含 mark + class;
	   gap字段,表示补齐padding。
	   array length:表示数组长度 
- VALUE:对应内存中当前存储的值; 
- Instance size:实例字节数值大小,
	   即:一个空的java数组,其实例大小为32Byte。
	   怎么计算?mark + class +  array length + gap = 32. 
-  Space losses: 0 bytes internal + 4 bytes external
   = 4 bytes total:表示内存补齐Padding,占用4个字节

2、padding对齐 vs 压缩验证

(1) 创建三个不同属性的类

public class Student {
    private String name;
    private Integer age;
}
public class StudentV1 {
    private String nameV1;
    private Integer ageV1;
    private int sexV1;
}
public class StudentV2 {
    private String nameV1;
    private Integer ageV1;
    private int sexV1;
    private boolean live;
}

(2) ClassLayout执行输出内存布局:

public static void main(String[] args) {
        System.out.println("有属性-对象布局 1================================");
        Student student = new Student();
        System.out.println(ClassLayout.parseInstance(student).toPrintable());

        System.out.println("有属性-对象布局 2================================");
        StudentV1 studentV1 = new StudentV1();
        System.out.println(ClassLayout.parseInstance(studentV1).toPrintable());


        System.out.println("有属性-对象布局 3================================");
        StudentV2 studentV2 = new StudentV2();
        System.out.println(ClassLayout.parseInstance(studentV2).toPrintable());

    }

(3) 输出结果:

有属性-对象布局 1================================
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.tsj.threads.Student object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                     (object header: class)    0xf800c143
 12   4    java.lang.String Student.name              null
 16   4   java.lang.Integer Student.age               null
 20   4                     (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

有属性-对象布局 2================================
com.tsj.threads.StudentV1 object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                     (object header: class)    0xf800f359
 12   4                 int StudentV1.sexV1           0
 16   4    java.lang.String StudentV1.nameV1          null
 20   4   java.lang.Integer StudentV1.ageV1           null
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

有属性-对象布局 3================================
com.tsj.threads.StudentV2 object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                     (object header: class)    0xf800f39e
 12   4                 int StudentV2.sexV1           0
 16   1             boolean StudentV2.live            false
 17   3                     (alignment/padding gap)   
 20   4    java.lang.String StudentV2.nameV1          null
 24   4   java.lang.Integer StudentV2.ageV1           null
 28   4                     (object alignment gap)    
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

(4) 结果分析:

- Student:
	Instance size: 24 bytes;
	24 bytes = mark(8) + class(4) + name(4,压缩) + age(4,压缩) + gap(4)
	name 类型:String,age类型:Integer,都属于引用类型。JDK默认开启了压缩,8位压缩至4位。
	进行了一次补齐,补齐4bytes
- StudentV1:
	Instance size: 24 bytes
	24 bytes = mark(8) + class(4) + sexV1(4) +  nameV1(4,压缩) + ageV1(4,压缩) 
	nameV1 类型:String,ageV1类型:Integer,都属于引用类型。JDK默认开启了压缩,8位压缩至4位。另外,sexV1类型为int,基本类型无法压缩。
	没有进行补齐
- StudentV2:
	Instance size: 32 bytes
	24 bytes = mark(8) + class(4) + sexV1(4) + live (1) + gap(3) + nameV1(4,压缩) + ageV1(4,压缩) + gap(4)
	进行了两次补齐,一次为internal,为live补齐了3bities,一次为external,为整个对象补齐4字节。

3、锁对象的内存布局

    主要是针对synchronized,JDK 1.6 优化了synchronized,提出了锁升级的机制,即无锁-偏向锁-轻量级锁-重量级锁,我这里不验证锁升级的过程,主要验证的数这四种锁在对象头是怎么存储和标识的。  

锁类型(按锁的状态分类)

  • non-biasable 无锁且不可偏向
  • biasable 无锁可偏向
  • biased 偏向锁
  • thin lock 轻量级锁
  • fat lock 重量级锁

锁标记

|--------------------------------------------------------------------------------------------------------------|
| Object Header (128 bits) |
|--------------------------------------------------------------------------------------------------------------|
| Mark Word (64 bits) | Klass Word (64 bits) |
|--------------------------------------------------------------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 无锁
|----------------------------------------------------------------------|--------|------------------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_lock_record:62 | lock:2 | OOP to metadata object | 轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | 重量锁
|----------------------------------------------------------------------|--------|------------------------------|
| | lock:2 | OOP to metadata object | GC
|--------------------------------------------------------------------------------------------------------------|

lock: 锁状态标记位,该标记的值不同,整个mark word表示的含义不同。

biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

从分布可以得出,看锁标记,直接看后 3 位即可,如果给到你锁类型标识,你只需要看前后2位就行。

biased_lock lock 16进制 状态
0 01 1 无锁
1 01 5 偏向
0 00 0 轻量
0 10 2 重量
0 11 3 GC

代码

public class SyncTest03 {
    public static void main(String[] args) throws InterruptedException {
        Student a = new Student();

        String str = ClassLayout.parseInstance(a).toPrintable();
        System.out.println("未上锁,输出ClassLayout");
        System.out.println(str);//01	无锁状态

        new Thread() {
            public void run() {
                synchronized (a) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        Thread.sleep(10);
        str = ClassLayout.parseInstance(a).toPrintable();
        System.out.println("第一次上锁,输出ClassLayout");
        System.out.println(str);//00	轻量级锁
        new Thread() {
            public void run() {
                synchronized (a) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        Thread.sleep(10);
        str = ClassLayout.parseInstance(a).toPrintable();
        System.out.println("第二次上锁,输出ClassLayout");
        System.out.println(str);//10	重量级锁
    }
}

无锁 =》轻量级/偏向锁 =》重量锁 

JDK默认开启偏向锁,但延时的,需要通过参数-XX:BiasedLockingStartupDelay=0禁用延时。(idea在VM Options设置就行了)
偏向锁的开启和禁止延迟参数:

// 关闭偏向锁延迟
-XX:BiasedLockingStartupDelay=0 

// 偏向锁的关闭
-XX:+UseBiasedLocking

设置-XX:BiasedLockingStartupDelay=0

输出结果:

未上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4                     (object header: class)    0xf800c143
 12   4    java.lang.String Student.name              null
 16   4   java.lang.Integer Student.age               null
 20   4                     (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

第一次上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x00007fce8f96e005 (biased: 0x0000001ff3a3e5b8; epoch: 0; age: 0)
  8   4                     (object header: class)    0xf800c143
 12   4    java.lang.String Student.name              null
 16   4   java.lang.Integer Student.age               null
 20   4                     (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

第二次上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x00007fce9801128a (fat lock: 0x00007fce9801128a)
  8   4                     (object header: class)    0xf800c143
 12   4    java.lang.String Student.name              null
 16   4   java.lang.Integer Student.age               null
 20   4                     (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结果分析:

上锁前
mark标识:0x0000000000000005 (biasable; age: 0)
(1)biasable; age: 0
	biasable:无锁可偏向
	age:年龄为0
(2)0x0000000000000005
	当前对象头为0x0000000000000005,二级制为:101

第一次上锁
mark标识:0x00007fce8f96e005 (biased: 0x0000001ff3a3e5b8; epoch: 0; age: 0)
(1)biased: 0x0000001ff3a3e5b8; epoch: 0; age: 0
	biased:表示偏向锁
	0x0000001ff3a3e5b8,二进制为:1111111110011101000111110010110111000 
(2)0x00007fce8f96e005 
	当前对象头为0x00007fce8f96e005,二进制为:11111111100111010001111100101101110000000000101

第二次上锁
mark标识:0x00007fce9801128a (fat lock: 0x00007fce9801128a)
(1)fat lock: 0x00007fce9801128a
	fat lock :表示重量级锁
	0x00007fce9801128a ,表示上锁对象头,二进制为:11111111100111010011000000000010001001010001010,查看后两位为10,10表示重量级锁
(2)0x00007fce9801128a 
	表示当前对象头为0x00007fce9801128a,二进制为:11111111100111010011000000000010001001010001010

加锁前对象处于无锁状态,加锁中处于偏向锁,同一时间再次访问同一锁后,偏向锁膨胀为重量级锁
注意,在偏向锁状态,当前对象头和上锁的对象头往往不是同一个。

设置-XX:+UseBiasedLocking

输出结果:

未上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                     (object header: class)    0xf800c143
 12   4    java.lang.String Student.name              null
 16   4   java.lang.Integer Student.age               null
 20   4                     (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

第一次上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x0000700008ebc9b0 (thin lock: 0x0000700008ebc9b0)
  8   4                     (object header: class)    0xf800c143
 12   4    java.lang.String Student.name              null
 16   4   java.lang.Integer Student.age               null
 20   4                     (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

第二次上锁,输出ClassLayout
com.tsj.threads.Student object internals:
OFF  SZ                TYPE DESCRIPTION               VALUE
  0   8                     (object header: mark)     0x00007f7bd001f13a (fat lock: 0x00007f7bd001f13a)
  8   4                     (object header: class)    0xf800c143
 12   4    java.lang.String Student.name              null
 16   4   java.lang.Integer Student.age               null
 20   4                     (object alignment gap)    
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结果分析:

未上锁:
mark标识:0x0000000000000001 (non-biasable; age: 0)
(1)non-biasable; age: 0
	non-biasable:无锁

第一次加锁,是真正加上锁
mark标识:0x0000700008ebc9b0 (thin lock: 0x0000700008ebc9b0)
(1)thin lock: 0x0000700008ebc9b0
	thin lock :轻量级锁
	0x0000700008ebc9b0,表示上锁的对象头,二进制为:11100000000000000001000111010111100100110110000,看后两位表示:00,00:表示轻量级锁
(2)0x0000700008ebc9b0
	对象头是:0x0000700008ebc9b0

第二次还是对同一把锁方法,存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
mark标识为:0x00007f7bd001f13a (fat lock: 0x00007f7bd001f13a)
(1)fat lock: 0x00007f7bd001f13a
	fat lock :表示重量级锁
	0x00007f7bd001f13a,上锁的对象头,二进制为:11111110111101111010000000000011111000100111010,看后两位表示:01,01:表示轻量级锁
(2)0x00007f7bd001f13a
	对象头是0x00007f7bd001f13a

加锁前对象处于无锁状态,加锁中处于轻量锁状态,同一时间再次访问同一锁后,轻量级锁膨胀为重量级锁

你可能感兴趣的:(JVM,java,jvm,开发语言)