覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

JOL - Javaオブジェクトのメモリサイズを取得する

JOLは、Javaのオブジェクトがメモリ上にどうレイアウトされているかを確認するためのツールで、OpenJDK のサイトで公開されています。
JOLはJava Object Layout の略です。


利用準備

JOLを利用するために複雑なセットアップは不要です。JOLのjarを実行環境のclasspathに通すだけで利用可能です。

jarの入手は以下に従ってください

■手動でダウンロードする場合

以下からダウンロードしてください

https://mvnrepository.com/artifact/org.openjdk.jol/jol-core


■Mavenを利用する場合

以下をpom.xmlに追記してください

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


JOLが提供するAPIの使い方

Javaの標準の機能では、対象インスタンスがどれだけのメモリを使用するのかを把握することは簡単ではありません。
が、JOLを使えばすぐに把握できます。

対象インスタンスのメモリサイズを取得したい

対象インスタンスのメモリサイズを取得するには以下のAPIを使用します。メモリサイズ(単位:byte)が戻り値で返されます。
GraphLayout#parseInstanceの引数にサイズ取得対象のインスタンスを指定します

long size = GraphLayout.parseInstance(obj).totalSize();

インスタンスの各フィールドのメモリサイズを取得したい

以下のAPIを使うとインスタンスの各フィールドがどれだけ容量を使用しているか等の細かな情報を知ることができます。

String footprint = GraphLayout.parseInstance(obj).toFootprint();

クラスのメモリレイアウトを取得したい

以下のAPIを使うとクラスのメモリレイアウトを知ることができます。
ClassLayout#parseClassの引数に調査対象のクラス(Classオブジェクト)を指定します

String layout = ClassLayout.parseClass(clazz).toPrintable();


以下実行サンプルです(全て64bitのJREで実行しています)


Stringのメモリサイズを調べる

String(length=1, 2, 3, 4, 5, 10)のインスタンスを生成して、それぞれの容量等を出力しています

■サンプルコード

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;

public class StringSizeCheck {

    private static final String str1 = "a";
    private static final String str2 = "ab";
    private static final String str3 = "abc";
    private static final String str4 = "abcd";
    private static final String str5 = "abcde";
    private static final String str10 = "abcdefghij";


    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(str1.getClass()).toPrintable());

        System.out.println("##################################################");

        System.out.println("String(length=1) totalSize(byte) -> " + GraphLayout.parseInstance(str1).totalSize());
        System.out.println("String(length=1) footprint -> \n" + GraphLayout.parseInstance(str1).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("String(length=2) totalSize(byte) -> " + GraphLayout.parseInstance(str2).totalSize());
        System.out.println("String(length=2) footprint -> \n" + GraphLayout.parseInstance(str2).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("String(length=3) totalSize(byte) -> " + GraphLayout.parseInstance(str3).totalSize());
        System.out.println("String(length=3) footprint -> \n" + GraphLayout.parseInstance(str3).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("String(length=4) totalSize(byte) -> " + GraphLayout.parseInstance(str4).totalSize());
        System.out.println("String(length=4) footprint -> \n" + GraphLayout.parseInstance(str4).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("String(length=5) totalSize(byte) -> " + GraphLayout.parseInstance(str5).totalSize());
        System.out.println("String(length=5) footprint -> \n" + GraphLayout.parseInstance(str5).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("String(length=10) totalSize(byte) -> " + GraphLayout.parseInstance(str10).totalSize());
        System.out.println("String(length=10) footprint -> \n" + GraphLayout.parseInstance(str10).toFootprint());
    }
}

■実行結果

Stringクラスそのもののメモリサイズが24byteとなっています。
アライメントの関係もあるので綺麗に連動しませんが、lengthが増えるごとにインスタンスのメモリサイズも大きくなっています。

java.lang.String object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                    VALUE
      0    12        (object header)                N/A
     12     4 char[] String.value                   N/A
     16     4    int String.hash                    N/A
     20     4        (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

##################################################
String(length=1) totalSize(byte) -> 48
String(length=1) footprint -> 
java.lang.String@22d8cfe0d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        24        24   [C
         1        24        24   java.lang.String
         2                  48   (total)


--------------------------------------------------
String(length=2) totalSize(byte) -> 48
String(length=2) footprint -> 
java.lang.String@46fbb2c1d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        24        24   [C
         1        24        24   java.lang.String
         2                  48   (total)


--------------------------------------------------
String(length=3) totalSize(byte) -> 48
String(length=3) footprint -> 
java.lang.String@5ef04b5d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        24        24   [C
         1        24        24   java.lang.String
         2                  48   (total)


--------------------------------------------------
String(length=4) totalSize(byte) -> 48
String(length=4) footprint -> 
java.lang.String@443b7951d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        24        24   [C
         1        24        24   java.lang.String
         2                  48   (total)


--------------------------------------------------
String(length=5) totalSize(byte) -> 56
String(length=5) footprint -> 
java.lang.String@69663380d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        32        32   [C
         1        24        24   java.lang.String
         2                  56   (total)


--------------------------------------------------
String(length=10) totalSize(byte) -> 64
String(length=10) footprint -> 
java.lang.String@4459eb14d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        40        40   [C
         1        24        24   java.lang.String
         2                  64   (total)


独自クラスのメモリサイズを調べる

Userという独自クラスのインスタンスを生成してそれぞれの容量等を出力しています

■サンプルコード

import lombok.Value;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;

public class POJOSizeCheck {

    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(User.class).toPrintable());

        System.out.println("##################################################");

        User user1 = new User("Sample Taro", 27);

        System.out.println("user1 totalSize(byte) -> " + GraphLayout.parseInstance(user1).totalSize());
        System.out.println("user1 footprint -> \n" + GraphLayout.parseInstance(user1).toFootprint());

        System.out.println("--------------------------------------------------");

        User user2 = new User("Sample Hanako", 35);

        System.out.println("user2 totalSize(byte) -> " + GraphLayout.parseInstance(user2).totalSize());
        System.out.println("user2 footprint -> \n" + GraphLayout.parseInstance(user2).toFootprint());
    }

    @Value
    static final class User {

        public static final String DEFAULT_NAME = "unknown_user";

        private String name;

        private int age;
    }
}

■実行結果

Userクラスそのもののメモリサイズが24byteとなっています。また、インスタンスuser1のメモリサイズが88byte, インスタンスuser2のメモリサイズが96byte になっています

jp.co.tmx.jol.sample.POJOSizeCheck$User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                    VALUE
      0    12        (object header)                N/A
     12     4    int User.age                       N/A
     16     4 String User.name                      N/A
     20     4        (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

##################################################
user1 totalSize(byte) -> 88
user1 footprint -> 
jp.co.tmx.jol.sample.POJOSizeCheck$User@22d8cfe0d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        40        40   [C
         1        24        24   java.lang.String
         1        24        24   jp.co.tmx.jol.sample.POJOSizeCheck$User
         3                  88   (total)


--------------------------------------------------
user2 totalSize(byte) -> 96
user2 footprint -> 
jp.co.tmx.jol.sample.POJOSizeCheck$User@5ef04b5d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        48        48   [C
         1        24        24   java.lang.String
         1        24        24   jp.co.tmx.jol.sample.POJOSizeCheck$User
         3                  96   (total)


配列のメモリサイズ取得(int[])

int配列(length=1, 2, 3, 4, 5, 10))のそれぞれのメモリサイズ等を出力しています

■サンプルコード

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;

public class ArraySizeCheck {

    private static final int[] intArray1 = new int[] {1};
    private static final int[] intArray2 = new int[] {1, 2};
    private static final int[] intArray3 = new int[] {1, 2, 3};
    private static final int[] intArray4 = new int[] {1, 2, 3, 4};
    private static final int[] intArray5 = new int[] {1, 2, 3, 4, 5};
    private static final int[] intArray10 = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(intArray1.getClass()).toPrintable());

        System.out.println("##################################################");

        System.out.println("int[](size=1) totalSize(byte) -> " + GraphLayout.parseInstance(intArray1).totalSize());
        System.out.println("int[](size=1) footprint -> \n" + GraphLayout.parseInstance(intArray1).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("int[](size=2) totalSize(byte) -> " + GraphLayout.parseInstance(intArray2).totalSize());
        System.out.println("int[](size=2) footprint -> \n" + GraphLayout.parseInstance(intArray2).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("int[](size=3) totalSize(byte) -> " + GraphLayout.parseInstance(intArray3).totalSize());
        System.out.println("int[](size=3) footprint -> \n" + GraphLayout.parseInstance(intArray3).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("int[](size=4) totalSize(byte) -> " + GraphLayout.parseInstance(intArray4).totalSize());
        System.out.println("int[](size=4) footprint -> \n" + GraphLayout.parseInstance(intArray4).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("int[](size=5) totalSize(byte) -> " + GraphLayout.parseInstance(intArray5).totalSize());
        System.out.println("int[](size=5) footprint -> \n" + GraphLayout.parseInstance(intArray5).toFootprint());

        System.out.println("--------------------------------------------------");


        System.out.println("int[](size=10) totalSize(byte) -> " + GraphLayout.parseInstance(intArray10).totalSize());
        System.out.println("int[](size=10) footprint -> \n" + GraphLayout.parseInstance(intArray10).toFootprint());
    }
}

■実行結果

アライメントの関係もあるので綺麗に連動しませんが、配列の要素数が増えるにしたがってメモリサイズも増えています

[I object internals:
 OFFSET  SIZE  TYPE DESCRIPTION                    VALUE
      0    16       (object header)                N/A
     16     0   int [I.<elements>                  N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

##################################################
int[](size=1) totalSize(byte) -> 24
int[](size=1) footprint -> 
[I@7aec35ad footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        24        24   [I
         1                  24   (total)


--------------------------------------------------
int[](size=2) totalSize(byte) -> 24
int[](size=2) footprint -> 
[I@5387f9e0d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        24        24   [I
         1                  24   (total)


--------------------------------------------------
int[](size=3) totalSize(byte) -> 32
int[](size=3) footprint -> 
[I@6e5e91e4d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        32        32   [I
         1                  32   (total)


--------------------------------------------------
int[](size=4) totalSize(byte) -> 32
int[](size=4) footprint -> 
[I@2cdf8d8ad footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        32        32   [I
         1                  32   (total)


--------------------------------------------------
int[](size=5) totalSize(byte) -> 40
int[](size=5) footprint -> 
[I@30946e09d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        40        40   [I
         1                  40   (total)


--------------------------------------------------
int[](size=10) totalSize(byte) -> 56
int[](size=10) footprint -> 
[I@5cb0d902d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        56        56   [I
         1                  56   (total)


Collectionのメモリサイズ取得(ArrayList)

Integerを要素として持つjava.util.ArrayListについて、要素数=1, 2, 3, 4, 5, 10の場合のメモリサイズ等を出力しています

■サンプルコード

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;

public class IntArrayListSizeCheck {

    private static final List<Integer> list1 = new ArrayList<Integer>();
    private static final List<Integer> list2 = new ArrayList<Integer>();
    private static final List<Integer> list3 = new ArrayList<Integer>();
    private static final List<Integer> list4 = new ArrayList<Integer>();
    private static final List<Integer> list5 = new ArrayList<Integer>();
    private static final List<Integer> list10 = new ArrayList<Integer>();

    static {
        list1.addAll(Arrays.asList(1));
        list2.addAll(Arrays.asList(1, 2));
        list3.addAll(Arrays.asList(1, 2, 3));
        list4.addAll(Arrays.asList(1, 2, 3, 4));
        list5.addAll(Arrays.asList(1, 2, 3, 4, 5));
        list10.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }

    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(list1.getClass()).toPrintable());

        System.out.println("##################################################");

        System.out.println("List(size=1) totalSize(byte) -> " + GraphLayout.parseInstance(list1).totalSize());
        System.out.println("List(size=1) footprint -> \n" + GraphLayout.parseInstance(list1).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("List(size=2) totalSize(byte) -> " + GraphLayout.parseInstance(list2).totalSize());
        System.out.println("List(size=2) footprint -> \n" + GraphLayout.parseInstance(list2).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("List(size=3) totalSize(byte) -> " + GraphLayout.parseInstance(list3).totalSize());
        System.out.println("List(size=3) footprint -> \n" + GraphLayout.parseInstance(list3).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("List(size=4) totalSize(byte) -> " + GraphLayout.parseInstance(list4).totalSize());
        System.out.println("List(size=4) footprint -> \n" + GraphLayout.parseInstance(list4).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("List(size=5) totalSize(byte) -> " + GraphLayout.parseInstance(list5).totalSize());
        System.out.println("List(size=5) footprint -> \n" + GraphLayout.parseInstance(list5).toFootprint());

        System.out.println("--------------------------------------------------");

        System.out.println("List(size=10) totalSize(byte) -> " + GraphLayout.parseInstance(list10).totalSize());
        System.out.println("List(size=10) footprint -> \n" + GraphLayout.parseInstance(list10).toFootprint());
    }
}

■実行結果

要素数が増えるごとにメモリサイズも大きくなっていることが分かります

java.util.ArrayList object internals:
 OFFSET  SIZE     TYPE DESCRIPTION                    VALUE
      0    12          (object header)                N/A
     12     4      int AbstractList.modCount          N/A
     16     4      int ArrayList.size                 N/A
     20     4 Object[] ArrayList.elementData          N/A
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

##################################################
List(size=1) totalSize(byte) -> 96
List(size=1) footprint -> 
java.util.ArrayList@1de0aca6d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        56        56   [Ljava.lang.Object;
         1        16        16   java.lang.Integer
         1        24        24   java.util.ArrayList
         3                  96   (total)


--------------------------------------------------
List(size=2) totalSize(byte) -> 112
List(size=2) footprint -> 
java.util.ArrayList@14514713d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        56        56   [Ljava.lang.Object;
         2        16        32   java.lang.Integer
         1        24        24   java.util.ArrayList
         4                 112   (total)


--------------------------------------------------
List(size=3) totalSize(byte) -> 128
List(size=3) footprint -> 
java.util.ArrayList@4459eb14d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        56        56   [Ljava.lang.Object;
         3        16        48   java.lang.Integer
         1        24        24   java.util.ArrayList
         5                 128   (total)


--------------------------------------------------
List(size=4) totalSize(byte) -> 144
List(size=4) footprint -> 
java.util.ArrayList@6659c656d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        56        56   [Ljava.lang.Object;
         4        16        64   java.lang.Integer
         1        24        24   java.util.ArrayList
         6                 144   (total)


--------------------------------------------------
List(size=5) totalSize(byte) -> 160
List(size=5) footprint -> 
java.util.ArrayList@2328c243d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        56        56   [Ljava.lang.Object;
         5        16        80   java.lang.Integer
         1        24        24   java.util.ArrayList
         7                 160   (total)


--------------------------------------------------
List(size=10) totalSize(byte) -> 240
List(size=10) footprint -> 
java.util.ArrayList@45283ce2d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        56        56   [Ljava.lang.Object;
        10        16       160   java.lang.Integer
         1        24        24   java.util.ArrayList
        12                 240   (total)


Collectionのメモリサイズ取得(HashSet)

Stringを要素として持つjava.util.HashSetについて、要素数=1, 5, 10の場合のメモリサイズ等を出力しています

■サンプルコード

import java.util.HashSet;
import java.util.Set;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;

public class HashSetSizeCheck {

    public static void main(String[] args) {
        System.out.println(ClassLayout.parseClass(HashSet.class).toPrintable());

        System.out.println("##################################################");

        Set<String> strSet = new HashSet<>();
        strSet.add("a");

        System.out.println("hashSet(size=1) totalSize(byte) -> " + GraphLayout.parseInstance(strSet).totalSize());
        System.out.println("hashSet(size=1) footprint -> \n" + GraphLayout.parseInstance(strSet).toFootprint());

        System.out.println("--------------------------------------------------");

        strSet.add("b");
        strSet.add("c");
        strSet.add("d");
        strSet.add("e");

        System.out.println("hashSet(size=5) totalSize(byte) -> " + GraphLayout.parseInstance(strSet).totalSize());
        System.out.println("hashSet(size=5) footprint -> \n" + GraphLayout.parseInstance(strSet).toFootprint());

        System.out.println("--------------------------------------------------");

        strSet.add("f");
        strSet.add("g");
        strSet.add("h");
        strSet.add("i");
        strSet.add("j");

        System.out.println("hashSet(size=10) totalSize(byte) -> " + GraphLayout.parseInstance(strSet).totalSize());
        System.out.println("hashSet(size=10) footprint -> \n" + GraphLayout.parseInstance(strSet).toFootprint());
    }
}

■実行結果

要素数=1の場合は240byte, 要素数=5の場合は560byte, 要素数=10の場合は960byteのメモリサイズであることが分かります。

java.util.HashSet object internals:
 OFFSET  SIZE    TYPE DESCRIPTION                    VALUE
      0    12         (object header)                N/A
     12     4 HashMap HashSet.map                    N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

##################################################
hashSet(size=1) totalSize(byte) -> 240
hashSet(size=1) footprint -> 
java.util.HashSet@1de0aca6d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        24        24   [C
         1        80        80   [Ljava.util.HashMap$Node;
         1        16        16   java.lang.Object
         1        24        24   java.lang.String
         1        48        48   java.util.HashMap
         1        32        32   java.util.HashMap$Node
         1        16        16   java.util.HashSet
         7                 240   (total)


--------------------------------------------------
hashSet(size=5) totalSize(byte) -> 560
hashSet(size=5) footprint -> 
java.util.HashSet@1de0aca6d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         5        24       120   [C
         1        80        80   [Ljava.util.HashMap$Node;
         1        16        16   java.lang.Object
         5        24       120   java.lang.String
         1        48        48   java.util.HashMap
         5        32       160   java.util.HashMap$Node
         1        16        16   java.util.HashSet
        19                 560   (total)


--------------------------------------------------
hashSet(size=10) totalSize(byte) -> 960
hashSet(size=10) footprint -> 
java.util.HashSet@1de0aca6d footprint:
     COUNT       AVG       SUM   DESCRIPTION
        10        24       240   [C
         1        80        80   [Ljava.util.HashMap$Node;
         1        16        16   java.lang.Object
        10        24       240   java.lang.String
         1        48        48   java.util.HashMap
        10        32       320   java.util.HashMap$Node
         1        16        16   java.util.HashSet
        34                 960   (total)


利用時の制約

JOLはJava7以上で利用する必要があります。



関連エントリ