Javaのオブジェクトサイズの測り方、sizeof

2009/03/01 追記

こちらにより新しい便利なツールが紹介されています。
いー ドット ぷりんとすたっくとれーす: Javaのインスタンスが使っているメモリサイズをみたい


↓以前の日記はここから


Javaではオブジェクトのサイズは謎だった。
プリミティブな値のサイズは仕様書にて指定されているのだが、オブジェクトのサイズは仕様外であるので実装依存である。

またオブジェクトのサイズを測るAPIがJ2SDK1.4までは存在しなかったのでオブジェクトのサイズは重要な割りに謎だった。
特にOutOfMemoryを頻出するJ2EE開発者には咽喉から手が出るくらい欲しい機能であったと思う。*1

この謎を解明してくれたのがVladimir Roubtsovさんだ。

Java Tip 130: Do you know your data size? | JavaWorld
Sizeof for Java | JavaWorld

上のJavaTipsは日本の雑誌JavaWorldにて翻訳が出版されているのでバックナンバーが手に入る人は探すと良いだろう。


Vladimirさんの方法を少しブラッシュアップしたものがGoogleでは現在トップになっていたのでそちらも紹介しておく。*2
こちらはMartin Rothさん。

http://martin.nobilitas.com/java/sizeof.html


どうやって測定しているかはお二人の記事を読んでいただくことにして、ではTigerではどうなったかというのを紹介する。

Tigerではjava.lang.insturument.InstrumentationにてgetObjectSizeが利用できるようになった。
こちらはMichael Nascimentoさん。
http://weblogs.java.net/blog/mister__m/archive/2004/02/playing_with_th_1.html

ところがこのMichaelさんのプログラム、動かないのだ。
たぶん記事が古くて現行のJavaSE5.0に対応していないのだと思う。*3
問題点は2つ、

コンパイルできないのにはほとほとまいった。
私はGenericsにまだ詳しくないのでさっぱりわからなかった。それにSunのTigerの日本語エラーメッセージがちっともわからない。*4
泣く泣く、諦めて放っておいたのだが、Eclipse3.1.2を使うとなんとなくわかる英語のエラーが表示されたので後は適当に勘で直してしまったのが次のパッチである。

public int compare(Object c1, Object c2) {
  return ((Class)c1).getName().compareTo(((Class)c2).getName());
}

この修正でもWarningだけはどうしても消すことができなかった。このためここ以下の記事は信頼性が皆無であるかもしれないので御注意願いたい。


さて、次に実行できない問題。
これは一瞬悩んだがjavaコマンドのヘルプを見て理由はすぐわかった。
Michaelさんはオプションに直接クラスを渡しているが、現在はjarを作成しなければダメなのである。
ここでjarを素直に作っただけだとまた実行できない。
java.lang.instrumentのJavaDocを読むとわかるのだが、このパッケージを用いる場合にはManifestに特別な記述事項がいるのだ。
後から知ったのだが有名な桜庭さんの虎の穴にて解説が詳しい。*5

J2SE 5.0 Tiger 虎の穴 Instrumentation

今回の実行のためにはまず適当なファイルを作り、次の1行を記述する。

Premain-Class: InstrumentationTest

ここではmanifest.txtとして保存した。
次にjarコマンドを次のように記述してjarを作成する。

jar cvfm InstrumentationTest.jar manifest.txt *.class

無名クラスがあるので注意。
以上で次のように入力すれば実行されるはずである。

java -ea -javaagent:InstrumentationTest.jar -cp . InstrumentationTest

実行結果*6

Size of Object: 8
Size of direct subclass: 8
Size of Calendar: 112

さて、ここで注意しておかなければならないことがある。
Vladimirさんの測定方法ではStringのような容量の変わるオブジェクトのサイズを量ることができる。しかしgetObjectSizeはオブジェクトの参照先のオブジェクトサイズまでは量ってくれない。
このためStringはいくら文字列の長さを変更しても同じ値を示す。

これには解決方法があり、Stringで表示される値とcharの配列の値を足すとVladimirさんの値と同じ正確な値になる。
よってオブジェクトをリフレクションにて再帰的に測定し、和を求めれば良いわけだ。
この記事の真の目的は誰かそれ作ってください、である。(笑
VladimirさんのプログラムはJDK5.0でも問題なく動いたのでそれを使ってもよいのだが、標準仕様であるgetObjectSizeのほうが使い勝手が良いのは確かだ。

さて、最後にもう2つだけ注意しておこう。
1つはJavaのオブジェクトサイズはプラットフォーム依存でなく、実装依存である。CPU,OSだけでなく、JVM実装メーカーでも値は異なる。これはJVMと同時に配布されるクラスライブラリにも手を入れているためである。
今1つはJavaのオブジェクトサイズを測定してみるとわかるが、Stringは意外なほどに大きい。これはVladimirさんのResultページを読むと良くわかる。数値を数文字列にすると非常にメモリ効率が悪いので注意が必要である。RDBでCHARを用いて数文字列を利用する場合に、Javaで数文字列を利用するとメモリ効率的には大きな損である。RDBでもJavaでもできる限り数値型を利用することがパフォーマンス的にも、メモリ効率的にも良いと言える。

(追記)
Netbeansのモジュール、Insaneを使うとより簡単にオブジェクトサイズを量ることができます。
こちらにまとめましたのでついでにお読みください。
Netbeansのinsaneを用いてJavaのsizeofを実装する - minghaiの日記

*1:メモリリークとは限らない。早く64bitJVMに移行したいものだ。

*2:日付からVladimirさんのほうが先だと判断した。

*3:記事が書かれた時期は恐らくβであり、Genericsは変更が多かったはずだ。

*4:Windowsで英語のエラーを表示させる方法を御存知の方は教えてください

*5:というか、先に読んでれば苦労しなかったのだけど(--;

*6:22行からのprintlnループは出力が多いので取った