Java利用JOL工具分析对象分布

文章目录

  • 对象的组成
    • 对象头[Header]
    • 实例数据[Instance Data]
    • 内存对齐[Padding]
  • JOL工具分析对象
    • Java项目引入依赖
    • 创建对象与结果分析
      • 创建简单无锁对象
      • 输出结果分析
      • 创建有属性的对象
      • 输出结果分析
      • 创建数组
      • 结果输出分析
      • 创建重量级锁对象
      • 输出结果分析
    • 局限性
  • 参考文章:

对象的组成

Java利用JOL工具分析对象分布_第1张图片

对象头[Header]

  1. Markword:存储对象自身运行时数据如hashcode、gc分代年龄等,64位系统总共占用8个字节,关于Markword详细内存分布如下
    Java利用JOL工具分析对象分布_第2张图片
  2. 类型指针:对象指向类元数据地址的指针,jdk8默认开启指针压缩,64位系统占4个字节
  3. 数组长度:若对象不是数组,则不分配空间大小,若是数组,则为4个字节长度

基于上面描述数组和普通对象的对象头有不同的内存大小,主要区别在于数组长度
Java利用JOL工具分析对象分布_第3张图片

实例数据[Instance Data]

指的就是对象中各个属性大小,比如User中name和age的内存大小

public class User{
   String name;
   int age;
}

若开启了类型指针压缩,String是4个字节, int是4个字节,属性填充总共8个字节

内存对齐[Padding]

因为JVM要求java的对象占的内存大小应该是8bit的倍数,所以后面有几个字节用于把对象的大小补齐至8字节的倍数,就不特别介绍了

JOL工具分析对象

Java项目引入依赖

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

创建对象与结果分析

创建简单无锁对象

public static void main(String[] args) {
        ClassLayout classLayout = ClassLayout.parseInstance(new Object());
        System.out.println(classLayout.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

OFFSET代表内存偏移量,单位是字节
SIZE代表占用内存大小,单位字节
TYPE DESCRIPTION 类型描述,其中​​object header​​为对象头
VALUE代表存储值,对应内存中当前存储的值;

对象总大小为16个字节,对象头占用12个字节,前8个字节代表markword,对象运行时数据状态,前64位的value倒序排列拼的markword是16进制为00 00 00 00 00 00 00 01,最后一个字节01的二进制是00000001,最后三位001,代表无锁状态,后4个字节代表对象指向类元数据的指针;空对象没有属性所以实例数据不占内存,对象头+实例数据=12,不是8个倍数,所以补4个字节为16字节

enum {  locked_value              = 0, // 0 00 轻量级锁
         unlocked_value           = 1,// 0 01 无锁
         monitor_value            = 2,// 0 10 重量级锁
         marked_value             = 3,// 0 11 gc标志
         biased_lock_pattern      = 5 // 1 01 偏向锁
  };

创建有属性的对象

public class ClassLayOutCheck {
    public static void main(String[] args) {
        ClassLayout classLayout = ClassLayout.parseInstance(new User());
        System.out.println(classLayout.toPrintable());

    public static class User {
        String name;
        int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }
}

输出结果分析

org.example.object.ClassLayOutCheck$User 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4                int User.age                                  1
     16     4   java.lang.String User.name                                 (object)
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

User对象总大小为24个字节,对象头占用12个字节,前8个字节代表markword,对象运行时数据状态,前8个字节低3位001代表无锁状,后4个字节代表对象指向类元数据的指针;User实例数据是8个字节,有属性name和age,其中name是String类型,不过这里有个问题,显示string的字节长度是4,value显示object,我们都知道内部实现是byte[],其中4字节指代对象内存指针,age是int类型,默认4个字节,value直接显示值;对象头+实例数据=20不是8字节倍数,所以填充4个字节为24个字节

创建数组

public static void main(String[] args) {
        ClassLayout classLayoutInt = ClassLayout.parseInstance(new int[]{});
        System.out.println("----------------------");
        System.out.println(classLayoutInt.toPrintable());
    }
[I 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)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     16     0    int [I.<elements>                             N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

结果输出分析

对象总大小为16个字节,对象头占用16个字节,前8个字节代表markword,对象运行时数据状态,低2位00代表轻量级锁,其他位是指向栈针中锁记录的指针,4个字节代表对象指向类元数据的指针,最后4字节代表数字的长度;空数组没有属性所以实例数据不占内存,对象头+实例数据=16,是8字节的倍数,不需要填充数据

创建重量级锁对象

public static void main(String[] args) {
        User l = new User("cyl",1);
        Runnable runnable = () -> {

            synchronized (l) {

                ClassLayout layout = ClassLayout.parseInstance(l);
                System.out.println(layout.toPrintable());
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        };

        for (int i = 0; i < 3; i++) {
            new Thread(runnable).start();
        }

    }


    public static class User {
        String name;
        Integer age;

        User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }


        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }
    ```
    
```java
org.example.object.ClassLayOutCheckThread$User object internals:
 OFFSET  SIZE                TYPE DESCRIPTION                               VALUE
      0     4                     (object header)                           5a 1d 04 43 (01011010 00011101 00000100 01000011) (1124343130)
      4     4                     (object header)                           ab 7f 00 00 (10101011 01111111 00000000 00000000) (32683)
      8     4                     (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    java.lang.String User.name                                 (object)
     16     4   java.lang.Integer User.age                                  1
     20     4                     (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

org.example.object.ClassLayOutCheckThread$User object internals:
 OFFSET  SIZE                TYPE DESCRIPTION                               VALUE
      0     4                     (object header)                           5a 1d 04 43 (01011010 00011101 00000100 01000011) (1124343130)
      4     4                     (object header)                           ab 7f 00 00 (10101011 01111111 00000000 00000000) (32683)
      8     4                     (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    java.lang.String User.name                                 (object)
     16     4   java.lang.Integer User.age                                  1
     20     4                     (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

org.example.object.ClassLayOutCheckThread$User object internals:
 OFFSET  SIZE                TYPE DESCRIPTION                               VALUE
      0     4                     (object header)                           5a 1d 04 43 (01011010 00011101 00000100 01000011) (1124343130)
      4     4                     (object header)                           ab 7f 00 00 (10101011 01111111 00000000 00000000) (32683)
      8     4                     (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    java.lang.String User.name                                 (object)
     16     4   java.lang.Integer User.age                                  1
     20     4                     (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

header倒叙最后一个字节是5a,对应二进制是01011010,查看最后三位010,可知对象状态是重量级锁

创建轻量级锁对象

public static void main(String[] args) {
        User l = new User("cyl",1);
        Runnable runnable = () -> {

            synchronized (l) {

                ClassLayout layout = ClassLayout.parseInstance(l);
                System.out.println(layout.toPrintable());
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        };

        for (int i = 0; i < 1; i++) {
            new Thread(runnable).start();
        }

    }


    public static class User {
        String name;
        Integer age;

        User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }


        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

输出结果分析

org.example.object.ClassLayOutCheckThread$User object internals:
 OFFSET  SIZE                TYPE DESCRIPTION                               VALUE
      0     4                     (object header)                           00 c9 3f 09 (00000000 11001001 00111111 00001001) (155175168)
      4     4                     (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4                     (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    java.lang.String User.name                                 (object)
     16     4   java.lang.Integer User.age                                  1
     20     4                     (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

header倒叙最后一个字节是00,对应二进制是00000000,查看最后三位000,可知对象状态是轻量级锁

局限性

当对象中嵌套对象时,得出Instance size这是当前对象内存大小,不是所有内存大小,比如set对象,实例属性是map,4个字节表示的对象的引用指针,而不是内部map的实际大小

java.util.HashSet 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)                           1c 72 00 f8 (00011100 01110010 00000000 11111000) (-134188516)
     12     4   java.util.HashMap HashSet.map                               (object)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

可以清楚的看到实例属性java.util.HashMap HashSet.map 的value
是object,4个字节代表它的引用指针。查询实际Java对象中嵌套对象的实际内存大小,可以自己写工具,主要就是递归直到事最基础数据类型,计算出来之后再累加,或者使用已经成熟的工具,参考博客:
两种工具查询Java嵌套对象引用内存大小

参考文章:

1.https://blog.csdn.net/superfjj/article/details/106582678
2.https://blog.51cto.com/u_15485936/5111767
3.https://blog.csdn.net/qq_38824137/article/details/107089862

你可能感兴趣的:(#,JAVA基础,java,jvm,开发语言)