Java 對象內存占用和對象頭分析

前言

普通對象的內存布局:

  • 1)Mark Word, 8個字節(jié)
  • 2)Class Pointer,如果是 32G 內存以下的,默認開啟對象指針壓縮,4 個字節(jié)
  • 3)數據區(qū)
  • 4)Padding(內存對齊),按照 8 的倍數對齊

數組對象的內存布局:

  • 1)Mark Word, 8個字節(jié)
  • 2)Class Pointer,如果是 32G 內存以下的,默認開啟對象指針壓縮,4個字節(jié)
  • 3)數組長度,4 個字節(jié)
  • 4)數據區(qū)
  • 5)Padding(內存對齊),按照 8 的倍數對齊
對象的內存布局

原生類型內存占用如下圖所示:

原生類型占用內存

RamUsageEstimator(計算 Java 對象內存占用)

簡介

RamUsageEstimator 是根據 Java 對象在堆內存中的存儲格式,通過計算 Java 對象頭、實例數據、引用等的大小,相加而得,如果有引用,還能遞歸計算引用對象的大小。

缺點:這種方式計算所得的對象頭大小是基于 JVM 聲明規(guī)范的,并不是通過運行時內存地址計算而得,存在與實際大小不符的這種可能性。

依賴

<!-- 計算 Java 對象內存占用工具 -->
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-core</artifactId>
      <version>4.0.0</version>
    </dependency>

常用方法 API

//計算指定對象及其引用樹上的所有對象的綜合大小,單位字節(jié)
long RamUsageEstimator.sizeOf(Object obj)

//計算指定對象本身在堆空間的大小,單位字節(jié)
long RamUsageEstimator.shallowSizeOf(Object obj)

//計算指定對象及其引用樹上的所有對象的綜合大小,返回可讀的結果,如:2KB
String RamUsageEstimator.humanSizeOf(Object obj)

演示代碼

sizeOf() 方法演示:

import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.util.RamUsageEstimator;

@Slf4j
public class RamUsageEstimatorDemo {

    public static void main(String[] args) {
        // 12(Header) + 0(Instance Data) + 4(Padding) = 16 bytes
        log.info("sizeOf(new Object()) = {} bytes", RamUsageEstimator.sizeOf(new Object()));

        // 12(Header) + 1(Instance Data) + 3(Padding) = 16 bytes
        log.info("sizeOf(boolean) = {} bytes", RamUsageEstimator.sizeOf(true));
        log.info("sizeOf(byte) = {} bytes", RamUsageEstimator.sizeOf((byte)2));

        // 12(Header) + 2(Instance Data) + 2(Padding) = 16 bytes
        log.info("sizeOf(char) = {} bytes", RamUsageEstimator.sizeOf('c'));
        log.info("sizeOf(short) = {} bytes", RamUsageEstimator.sizeOf((short)2));


        // 12(Header) + 4(Instance Data) + 0(Padding) = 16 bytes
        log.info("sizeOf(int) = {} bytes", RamUsageEstimator.sizeOf(2));
        log.info("sizeOf(float) = {} bytes", RamUsageEstimator.sizeOf((float)2.0));

        // 12(Header) + 8(Instance Data) + 4(Padding) = 24 bytes
        log.info("sizeOf(long) = {} bytes", RamUsageEstimator.sizeOf((long)2));
        log.info("sizeOf(double) = {} bytes", RamUsageEstimator.sizeOf(2.0));

        // 16(Header) + 0(Instance Data) + 0(Padding) = 16 bytes
        log.info("sizeOf(new int[]) = {} bytes", RamUsageEstimator.sizeOf(new int[]{}));

        // 16(Header) + 4(Instance Data) + 4(Padding) = 24 bytes
        log.info("sizeOf(new int[]) = {} bytes", RamUsageEstimator.sizeOf(new int[]{2}));

        // 16(Header) + 8(Instance Data) + 0(Padding) = 24 bytes
        log.info("sizeOf(new int[]) = {} bytes", RamUsageEstimator.sizeOf(new int[]{2, 2}));
    }
}

shallowSizeOf() 方法演示:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.util.RamUsageEstimator;

@Slf4j
public class RamUsageEstimatorReferenceDemo {

    public static void main(String[] args) {
        ReferenceData empty = new ReferenceData();
        ReferenceData full = ReferenceData.full();
        // 12(Header) + 8(Instance Data) + 4(Padding) = 24 bytes
        // 一個壓縮后的對象指針占用 4 bytes,兩個就是 8 bytes
        log.info("sizeOf(empty ReferenceData) = {} bytes", RamUsageEstimator.sizeOf(empty));
        log.info("humanSizeOf(empty ReferenceData) = {} bytes", RamUsageEstimator.humanSizeOf(empty));
        log.info("shallowSizeOf(empty ReferenceData) = {} bytes", RamUsageEstimator.shallowSizeOf(empty));

        System.out.println();

        // ReferenceData: 12(Header) + 8(Instance Data) + 4(Padding) = 24 bytes
        // Integer: 12(Header) + 4(Instance Data)  = 16 bytes
        // Long: 12(Header) + 8(Instance Data) + 4(Padding) = 24 bytes
        // total size = 24(ReferenceData) + 16(Integer) + 24(Long) + = 64 bytes
        log.info("sizeOf(full ReferenceData) = {} bytes", RamUsageEstimator.sizeOf(full));
        log.info("humanSizeOf(full ReferenceData) = {} bytes", RamUsageEstimator.humanSizeOf(full));


        // shallowSizeOf() 方法不會考慮字段引用對象占用的內存
        // 一個壓縮后的對象指針占用 4 bytes,兩個就是 8 bytes
        // 12(Header) + 8(Instance Data) + 4(Padding) = 24 bytes
        log.info("shallowSizeOf(full ReferenceData) = {} bytes", RamUsageEstimator.shallowSizeOf(full));
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ReferenceData {
        private Integer intVal;
        private Long longVal;

        public static ReferenceData full() {
            return new ReferenceData(2, 2L);
        }
    }
}

jol(查看對象頭的神器)

簡介

joljava object layout 的縮寫,即 Java 對象布局。
是一個可以在代碼中計算 Java 對象的大小以及查看 Java 對象內存布局的工具包。

依賴

    <!-- 查看對象頭的神器 -->
    <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.14</version>
    </dependency>

使用 jol 計算對象的大小

語法:

// 使用 jol 計算對象的大?。▎挝粸樽止?jié))
ClassLayout.parseInstance(obj).instanceSize() 

使用 Demo:

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;

@Slf4j
public class ClassLayoutDemo {

    public static void main(String[] args) {
        // 12(Header) + 0(Instance Data) + 4(Padding) = 16 bytes
        log.info("sizeOf(new Object()) = {} bytes", ClassLayout.parseInstance(new Object()).instanceSize());

        // 12(Header) + 1(Instance Data) + 3(Padding) = 16 bytes
        log.info("sizeOf(boolean) = {} bytes", ClassLayout.parseInstance(true).instanceSize());
        log.info("sizeOf(byte) = {} bytes", ClassLayout.parseInstance((byte) 2).instanceSize());

        // 12(Header) + 2(Instance Data) + 2(Padding) = 16 bytes
        log.info("sizeOf(char) = {} bytes", ClassLayout.parseInstance('c').instanceSize());
        log.info("sizeOf(short) = {} bytes", ClassLayout.parseInstance((short) 2).instanceSize());


        // 12(Header) + 4(Instance Data) + 0(Padding) = 16 bytes
        log.info("sizeOf(int) = {} bytes", ClassLayout.parseInstance(2).instanceSize());
        log.info("sizeOf(float) = {} bytes", ClassLayout.parseInstance((float) 2.0).instanceSize());

        // 12(Header) + 8(Instance Data) + 4(Padding) = 24 bytes
        log.info("sizeOf(long) = {} bytes", ClassLayout.parseInstance((long) 2).instanceSize());
        log.info("sizeOf(double) = {} bytes", ClassLayout.parseInstance(2.0).instanceSize());

        // 16(Header) + 0(Instance Data) + 0(Padding) = 16 bytes
        log.info("sizeOf(new int[]) = {} bytes", ClassLayout.parseInstance(new int[]{}).instanceSize());

        // 16(Header) + 4(Instance Data) + 4(Padding) = 24 bytes
        log.info("sizeOf(new int[]) = {} bytes", ClassLayout.parseInstance(new int[]{2}).instanceSize());

        // 16(Header) + 8(Instance Data) + 0(Padding) = 24 bytes
        log.info("sizeOf(new int[]) = {} bytes", ClassLayout.parseInstance(new int[]{2, 2}).instanceSize());
    }
}

RamUsageEstimator.sizeOf() 方法的結果一致。

使用 jol 查看對象的內存布局

語法:

// 使用 jol 查看對象的內存布局
ClassLayout.parseInstance(obj).toPrintable()

使用 Demo:

import org.openjdk.jol.info.ClassLayout;

public class ClassLayoutDemo {

    public static void main(String[] args) {
        // 12(Header) + 0(Instance Data) + 4(Padding) = 16 bytes
       System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());

        // 12(Header) + 1(Instance Data) + 3(Padding) = 16 bytes
        System.out.println(ClassLayout.parseInstance(true).toPrintable());
        System.out.println(ClassLayout.parseInstance((byte) 2).toPrintable());

        // 12(Header) + 2(Instance Data) + 2(Padding) = 16 bytes
        System.out.println(ClassLayout.parseInstance('c').toPrintable());
        System.out.println(ClassLayout.parseInstance((short) 2).toPrintable());


        // 12(Header) + 4(Instance Data) + 0(Padding) = 16 bytes
        System.out.println(ClassLayout.parseInstance(2).toPrintable());
        System.out.println(ClassLayout.parseInstance((float) 2.0).toPrintable());

        // 12(Header) + 8(Instance Data) + 4(Padding) = 24 bytes
        System.out.println(ClassLayout.parseInstance((long) 2).toPrintable());
        System.out.println(ClassLayout.parseInstance(2.0).toPrintable());

        // 16(Header) + 0(Instance Data) + 0(Padding) = 16 bytes
        System.out.println(ClassLayout.parseInstance(new int[]{}).toPrintable());

        // 16(Header) + 4(Instance Data) + 4(Padding) = 24 bytes
        System.out.println(ClassLayout.parseInstance(new int[]{2}).toPrintable());

        // 16(Header) + 8(Instance Data) + 0(Padding) = 24 bytes
        System.out.println(ClassLayout.parseInstance(new int[]{2, 2}).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.Boolean 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)                           dc 20 00 f8 (11011100 00100000 00000000 11111000) (-134209316)
     12     1   boolean Boolean.value                             true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

...

[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

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容