- 締切済み
オブジェクトのヒープサイズの取得
任意のオブジェクトの実行中のヒープサイズを取得する方法を教えてください。 具体的には、Undoバッファ(LinkedList)を実装していて、そのヒープサイズが あるサイズを超えた場合には、HDDに内容を保存してヒープを開放したいと考えています。 これを実現するために、動的にヒープサイズを取得する必要があるのですが、 また、上記のようにListがあるサイズを超えたら自動的にHDDなどに保存・呼び出しをしてくれて ユーザレベルでオンメモリがどうか等を意識しなくてもすむようなライブラリは 存在しないでしょうか? 以上2点について、よろしくお願いします。
- みんなの回答 (7)
- 専門家の回答
みんなの回答
- hirusagari
- ベストアンサー率64% (20/31)
ANo.6で挙げられたふたつの条件に加えて、 (3)OutOfMemoryErrorが起こりうるすべてのコードが、OutOfMemoryErrorが発生し処理が中断しても不整合が生じないように設計されていること という条件が必要だと思います。 そうでなければOutOfMemoryErrorが発生したそのオブジェクトは壊れているとみなさざるを得ません。 たとえば、ArrayList.set() で IndexOutOfBoundsException が発生しても、 それは予期された動作ですからArrayListは壊れていませんが、 OutOfMemoryErrorが起きたらそのArrayListは壊れていて、 もはや使い物にならないとみなしたほうがいいでしょう。 JavaのAPIにもOutOfMemoryErrorを予期しているメソッドほとんどないです。その大半が使えないでしょう。 >大きなデータオブジェクトを単純にアロケートしていくだけのアプリケーションでは、通常、満たされます とのことですが、「大きなデータオブジェクトを単純にアロケートしていくだけのアプリケーション」などまず存在しませんし、 「すべてのオブジェクトは、OutOfMemoryErrorが起きた際にエラーハンドリングが可能であるくらいに、 サイズが大きくなければならない」という奇妙な制限を加えてまでその方法をとる利点はないと思います。 また、JavaのAPIの大半は小さなオブジェクトをアロケートする可能性があるので、使用を避けねばならなくなります。 >事前に大きなヘッジメモリをアロケートしておいて、OOME時にそれを解放します。 そのような方法でなんとかエラーのハンドリングできるとしても、 エラーメッセージを表示したりエラーログを残すぐらいがせいぜいでしょう。 エラー発生直前に実行されていたコードはその機能を完遂することなく突然中断され、 不整合な状態になっている可能性がありますから、そのようなアプリケーションの実行を続けるのは危険です。 壊れたデータで大切なファイルを上書きしてしまうといった取り返しのつかないことをしてしまう前に、 アプリケーションを終了してしまったほうが安全でしょう。 >「メモリウォッチャースレッド」を動かす技法 >SoftReferenceやWeakReference そうですね。質問者のかたの要件には合いませんけれど、キャッシュを実現するならそれらが適当だと思います。 これらに比べれば、OutMfMemoryErrorのcatchなど冗談もいいところです。 >アプリケーションの設計を見直す(もっとメモリセーフな設計にする) それが可能なら最初からその設計をするべきです。 ですから、OutMfMemoryErrorのcatchなどせず、最初から安全な設計にすべきだと自分は言っているわけです。 >OOMEで単純にアプリケーションを終わらせる、などの結論になるでしょう。 その結論では質問者のかたが困ってしまいますよ。 >実例として具体的にご紹介できるオープンソースのソフト等は、今の私の知識内にはありません。 残念です……。 Google codesearch( http://www.google.com/codesearch ) で簡単に漁ってみたところ、 幾つか OutOfMemoryError を予期しているメソッドやcatchしているコードは見つかりました。 たとえばGNU classpthのjava.lang.reflect.Array#newInstance()がOutOfMemoryErrorを投げることがドキュメントに書かれていました。 catchしているものは、むしろThrowableからOutOfMemoryErrorをふるいわけ、 再びOutOfMemoryErrorを投げるために使われていることが多いようです。 OutOfMemoryErrorのハンドリングは難しいからでしょう。
- _ranco_
- ベストアンサー率58% (126/214)
たしかに、hirusagariさんがおっしゃるように、どんな技法にも…コンピュータやプログラミングに限らず…その技法が有効に使えるための前提条件がありますね。「OutOfMemoryErrorをcatchする」に関しては、(1)もれなく確実にcatchできること、と、(2)そのときに、有意義な処理ができる程度のメモリ残量があることが、基本的な前提条件です。これらの前提条件は、大きなデータオブジェクトを単純にアロケートしていくだけのアプリケーションでは、通常、満たされます。西暦2000年出版のJava Pitfallsで紹介されて以来、ときどき見かける(私も書いたことがある!)コードですが、実例として具体的にご紹介できるオープンソースのソフト等は、今の私の知識内にはありません。 そして、上の(1)の条件が満たされない場合、つまりOOMEが不特定多数の場所で予測不能に起こりうる場合には、独立のバックグラウンドスレッドとして「メモリウォッチャースレッド」を動かす技法があります。これは、一定の閾値(たとえば5メガバイト)を決めておいて、空きメモリの大きさがそれを下回ったら適切な対策を取ります(たとえば大きなデータ構造をclearする)。 また、(2)の条件が満たされない、つまりOOMEが起きたときメモリの余裕がない場合は、「ヘッジング」という技法がよく使われます。いわゆる、“ヘッジファンド”の“ヘッジ”です。事前に大きなヘッジメモリをアロケートしておいて、OOME時にそれを解放します。ヘッジを取り崩すわけですね。Eclipseが、この技法を使っているようです。もちろん、適切な対策を実行したあとで、ヘッジを再びアロケートしなければなりません。 OOME対策として上の三つの技法が使えない状況では、これまたよく見かける方法ですが、データオブジェクト(等)のパーシステンス化(バックアップ)は早期にやっておいて、それらのオブジェクトの参照をSoftReferenceやWeakReferenceに変えます。WeakReferenceを使っているMapであるjava.util.WeakHashMapを、便利に使えるアプリケーションもあるでしょう。 以上のどれも不可能なら、アプリケーションの設計を見直す(もっとメモリセーフな設計にする)、あるいは、OOMEで単純にアプリケーションを終わらせる、などの結論になるでしょう。
- hirusagari
- ベストアンサー率64% (20/31)
本項の質問文にあるように「HDDに内容を保存してヒープを開放したい」わけですから、 OutOfMemoryErrorが起きたときに簡単なログを残すサンプルを書いてみます。 import java.io.*; import java.util.*; public class Test { public static void main(String[] args) throws Exception{ LinkedList<byte[]> list = new LinkedList<byte[]>(); Runtime runtime = Runtime.getRuntime(); for(;;){ try{ list.add(new byte[0]); }catch(OutOfMemoryError e){ File file = new File("log.txt"); FileWriter writer = new FileWriter(file, true); writer.write(String.format("OutOfMemoryError occurred. The list size is %d. Free memory is %d byte.\n", list.size(), runtime.freeMemory())); writer.close(); list.clear(); } } } } これを結果を早く出すために -Xmx2048 のVMオプションをつけて、Windows XP上のJRE1.6.0_01で実行してみました。 すると new File() するところで再びOutOfMemoryErrorが発生し終了しました。 new byte[0] のところを new byte[1000000] ぐらいに書き換えて実行するとcatchで復旧しそのまま実行を続けられます。 これは new byte[1000000] が失敗したとき、まだある程度のメモリが残されているためですね。 それに対して new byte[0] で少しずつメモリを確保していくと、 new File() もできないくらいのまさに「ギリチョンのメモリ貧乏」になってしまいます。 つまり、大量にメモリを確保したときにうまくOutOfMemoryErrorすれば復旧できますが、 少量メモリを確保したときに運悪くOutOfMemoryErrorが発生すると落ちます。 確率の問題になり、とても安定しているとはいえないと思いますから、 自分ならそのようなコーディングを避けると思います。 そして、メモリの確保はコードのあらゆる部分で行われる可能性があり、 従ってOutOfMemoryErrorはコードのあらゆる位置で発生するおそれがありますから、 Undo/Redoの部分だけOutOfMemoryErrorをcatchしてもザル同然です。 コードのどの位置でOutOfMemoryErrorが発生しても復旧できるように書かねばならず、 自分の考えではそれはとてつもなく厄介なコーディングになる思います。 >多くの実用アプリでよく使われている よろしかったらどのアプリケーションで使われているか教えていただけませんか? ソースが公開されているものでしたら幸いです。 そのようなアプリケーションのコードがどうやって安定してOutOfMemoryErrorに対処しているのか学びたいと思いますので。
- _ranco_
- ベストアンサー率58% (126/214)
OutOfMemoryErrorをcatchしてメモリを解放するやりかたで、実用的/経験的にはOKですし、多くの実用アプリでよく使われている技法です。理由は、(1)物理的明示的にオンメモリの存在がはっきりしているリソースを解放するから、そして、(2)通常は、JVMがまったく何もできなくなるほどギリチョンのメモリ貧乏になることはないからです。 あなたのアプリの実際のニーズや実行シナリオに即して、下のようなテストプログラムを書いて動かしてみてください。 --------------------------- (全角スペース→半角スペースに) import java.util.*; public class OomeRecovery{ public static void main(String[] args){ ArrayList<Double[]> ad = new ArrayList<Double[]>(); Runtime rt = Runtime.getRuntime(); String s = ""; int counter = 100; while (--counter > 0){ s = ""; System.out.printf("Before: %12d ", rt.freeMemory()); try{ Double[] da = new Double[1000000]; ad.add(da); } catch (OutOfMemoryError e){ ad.clear(); s = " *"; } System.out.printf("After : %12d%s\n", rt.freeMemory(), s); } } } ----------------------------
- hirusagari
- ベストアンサー率64% (20/31)
#2です。 >Instrumentationのインスタンスの取得の方法がわかりませんでした。 #2で挙げたページに次のような記述があるのでお読みになりましたよね。 <quote> Instrumentation インタフェースのインスタンスを取得する方法は 2 つあります。 1.エージェントクラスを指定する方法で JVM を起動した場合。この場合、Instrumentation インスタンスは、そのエージェントクラスの premain メソッドに渡されます。 2.JVM の開始後にエージェントを開始する機構が JVM に用意されている場合。この場合、Instrumentation インスタンスは、そのエージェントコードの agentmain メソッドに渡されます。 これらの機構は、パッケージの仕様 で説明します。 </quote> また、上記で言及されているパッケージの仕様に以下の記述があるのをお読みになりましたよね。 <quote> JVM はエージェントクラスで最初に次のメソッドを呼び出そうとします。 public static void premain(String agentArgs, Instrumentation inst); </quote> Instrumentationのインスタンスはpremainの引数として取得できるようです。 従ってサンプルコードをおこすほどの内容ではありませんし、むしろ上記の「エージェント」の内容や起動方法がポイントのようです。 この記述のどの部分がわからないのでしょうか? わからない部分を具体的に挙げていただければお答えできるかもしれません。 ただし自分はこのパッケージは使ったことがないので、Javadocから読み取れるもの以外の内容は自分はお答えしかねます。 あと#1さんの補足になりますが、OutOfMemoryErrorが発生している状態でまともにコレクションをclear()できる保証はありません。 ましてやファイルを開いてUndoバッファを書き込みなんてとても出来そうにないですし、 正常にアプリケーションを復帰できる期待はもてませんのでお勧めはしません。
- hirusagari
- ベストアンサー率64% (20/31)
オブジェクトのサイズは java.lang.instrument.Instrumentation#getObjectSize で計れるようです。 http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/instrument/Instrumentation.html#getObjectSize(java.lang.Object) >ユーザレベルでオンメモリがどうか等を意識しなくてもすむ OSが提供する仮想メモリではだめでしょうか?
- _ranco_
- ベストアンサー率58% (126/214)
OutOfMemoryErrorをcatchして、LinkedList(など)をclear()するのが簡単ですね。
補足
ご回答ありがとうございました。 しかしながら、Instrumentationのインスタンスの取得の 方法がわかりませんでした。 Instrumentationのインスタンスを取得して、それを使って Listのヒープサイズを調べるところまでのサンプルを ご提供いただけないでしょうか? 以上、よろしくお願いします。