Netbeansのinsaneを用いてJavaのsizeofを実装する

先日、Javaオブジェクトのサイズの測定方法についてまとめた。
Javaのオブジェクトサイズの測り方、sizeof - minghaiの日記

この記事は思わぬ人気を呼び、はてブの注目記事として挙げて頂いた。またGoogleの検索結果から読みに来てくださる人が今でも多いようだ。
Javaのオブジェクトサイズに興味がある人はやはりとても多いようだ。

今日はNetBeansのモジュールとして開発されたInsaneを用いて、JDK1.4でも使用が可能で、JavaWorldのVladimirさんのプログラムよりも使用方法が簡単なsizeofを実装してみた。

Insaneの公式Webページはこちらになる。
NetBeans Insane - The postmortem memory leak analysis tool

NetBeansではこれを用いて、JUnitの拡張としてassertGC、assertSizeを用意しているそうだ。
http://xtest.netbeans.org/NbJUnit/NbJUnit-overview.html

assertSizeを用いれば、オブジェクトのサイズが想定以下であるかを検査することができる。想定サイズに0を指定すれば必ずassertは失敗し、実際のサイズを知ることができる。
しかし、この方法ではUnitTestのテストコードとして実装してしまうのであまり使い勝手が良くない。
そこでInsaneのみを用いてsizeofメソッドを実装することにした。

まず、Insaneは現在CVSにてのみ公開されている。
この場合ソースからビルドを行う必要ができてしまう。
そこで今回はNbJUnitの配布バイナリからInsaneのjarのみを用いることにした。
上記のNbJUnit公式WebページのDocumentation&Filesからダウンロードできるので用意してもらいたい。

sizeofメソッドのソースは以下のとおりである。

import java.util.Arrays;
import org.netbeans.insane.scanner.Filter;
import org.netbeans.insane.scanner.ScannerUtils;
import org.netbeans.insane.scanner.CountingVisitor;

class SizeTest {
	public static void main(String args[]) {
		System.out.println("Empty string size = " + getSizeOf(""));
		System.out.println("sizeof(Integer) = " + getSizeOf(new Integer(1)));
		System.out.println("sizeof(int[10]) = " + getSizeOf(new int[10]));
		System.out.println("sizeof(int[10][0]) = " + getSizeOf(new int[10][0]));
	}

	public static int getSizeOf(Object o) {
		Filter f = ScannerUtils.skipObjectsFilter(Arrays.asList(new Object[0]), false);
		CountingVisitor counter = new CountingVisitor();
		try {
			ScannerUtils.scan(f, counter, Arrays.asList(new Object[]{o}), false);
			return counter.getTotalSize();
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}
}

このプログラムを以下の要領でコンパイルする。
私の環境はWindowsXpCygwinなので御自分の環境に合わせて適当に変えて下さい。

javac -cp 'E:\nbjunit-1.30\netbeans\modules\ext\insanelib.jar' SizeTest.java

以下のように入力して実行を行うことができる。

$ java -cp 'E:\nbjunit-1.30\netbeans\modules\ext\insanelib.jar;.' SizeTest
Empty string size = 40
sizeof(Integer) = 16
sizeof(int[10]) = 56
sizeof(int[10][0]) = 216

実行結果を見るとVladimirさんのプログラムと同じ数値が表示されているようだ。

まだInsaneのソースは読んでいないのでどのように量っているのかはよくわからない。注意が必要な点としてどこに書いてあったのか忘れてしまったが、この実装はSun JVMのみにて正しい値となることが書いてあった。Vladimirさんのプログラムで既に検証済みだが、SunとIBMでは同じWindows上でもオブジェクトのサイズが違う。InsaneがSun依存のプログラムとなっている場合には、SunJVM以外での使用は注意が必要だ。この点に関しては追試ができしだいまた報告したい。

InsaneによるsizeofはTiger(JDK5.0)のInstrumentationのgetObjectSizeよりも使い勝手が良く非常に便利だと言える。
正確性がどの程度なのかわからないが、皆さんでぜひ試してみてもらいたい。

Insaneのソースを読んでわかったこと

Insaneのソースを少し読んだ。


ScannerUtilにはpublicなsizeOf(Object)というそのものズバリなメソッドがあった。このため、上のような実装を行わなくても直接Insaneを用いてオブジェクトのサイズを知ることができる。ただし、この方法ではオブジェクトのフィールドの参照先のオブジェクトのサイズを合計してくれない。このため、あくまでも全体の大きさが欲しい時には上のように実行しなければならない。


上のソースコード中のFilterを作成している行は実際には必要がなかった。ScannerUtils.scanメソッドの第1引数、つまりFilterにはnullが指定できる。nullを指定すれば同じ結果を算出する。


オブジェクトサイズの測定方法はオブジェクトのフィールドの型をリフレクションで判定し、そのサイズを合計する。これをSuperクラスに対し繰り返す方法である。つまりVladimirさんの第2の方法にほぼ等しい。
Sizeof for Java | JavaWorld


今のところ、ベース(java.lang.Objectのサイズ)が8とハードコードされているため、SunのJVMでしか正しい値は出ない。IBMのObjectは12であるからである。オブジェクト参照のサイズが4とこれもハードコーティングされているが、64bitのJVMでも同じになるのだろうか?残念ながら64bit環境がないのでこれが検証できない。どなたかVladimirさんの第1の方法かTigerの方法で検証して頂けたら嬉しい。


またInsaneはJDK5.0のInstrumentation.getObjectSizeと異なり、8バイト境界を必ず補正してくれる。この8バイト境界も64bitJVMで同じになるのかは不明だ。


InsaneのソースはThreadの使い方に迷いが見られたり、まだ発展途上に思えるが、使いやすさは実に素晴らしい。特にJDK1.3やJDK1.4をまだ用いているユーザでTigerのInstrumentationを使うことができない方々にお勧めする。