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以上で利用する必要があります。