第7章 ヒープのベストプラクティス
一般的には、オブジェクトを生成する回数を減らし、必要なくなったらすぐに廃棄すべきというルールがある。一方、同じ種類のオブジェクトをなんども生成するのは全体としてのパフォーマンスの悪化に繋がります。このようなオブジェクトを破棄せずに再利用するようにすれば、パフォーマンスは大きく向上します。
ヒープの分析
ヒープヒストグラム
どの型のオブジェクトが多くのメモリを消費しているかという点について、分析が必要です。このための最も簡単な方法が、ヒープヒストグラムを利用するというものです。ヒープヒストグラムを使えば、アプリケーション内で使われているオブジェクトの数を手早く確認できます。 ヒープヒストグラムのデータはとても小さいので、自動化されたテストの中で毎回出力されるようにするとよいでしょう。
ヒープダンプ
ヒープヒストグラムよりも詳細な分析が必要な場合には、ヒープダンプが必要になります。 ヒープの分析では、まず保持メモリ量(retained memory)について調べるのが一般的です。保持メモリ量とは、該当するオブジェクトがガーベジコレクションの対象になった場合に解放されるメモリ量を表します。 ヒープの中で保持メモリ量の多いオブジェクトはドミネータ(dominator)と呼ばれます。 一般的な指針としては、コレクションのエントリではなくコレクション自体のオブジェクトから探索を初めて、最大のコレクションを探すようにしましょう。
OutOfMemoryError
OutOfMemoryErrorは次のような状況で発生します。
- JVMから利用できるネイティブメモリが少ない場合
- permanent領域(Java7まで)やメタスペース(Java8)がいっぱいになった場合
- ヒープ自体がいっぱいになった場合(使用中のオブジェクトが多すぎ、ヒープとして設定された量を上回っています)
ヒープ自体がいっぱいの場合、このようなエラーメッセージが表示されます。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
このエラーが発生するのは、主にヒープメモリの不足が原因です。単に、使用中のオブジェクトが多すぎてヒープに収まらなくなったか、メモリリークが発生している可能性があるかのどちらかが考えられます。 どちらの場合でも、どのオブジェクトがメモリを最も多く消費しているかを知るにはヒープダンプの分析が必要です。
[ヒープダンプの自動取得] OutOfMemoryErrorの発生は予測ができないため、いつヒープダンプを取得するべきか判断するのは困難です。そこで、JVMには次のようなフラグが用意されています。 -XX:+HeapDumpOnOutOfMemoryError OutOfMemoryErrorの発生時にヒープダンプを生成 -XX:HeapDumpPath=パス ヒープダンプの出力先を指定 -XX:+HeapDumpAfterFullGC フルガベージコレクションの後にヒープダンプを生成 -XX:+HeapDumpBeforeFullGC フルガベージコレクションの前にヒープダンプを生成
アプリケーションはオブジェクトを追加するだけで解放はしないため、コレクションは、メモリリークを最も発生しやすいです。不必要になったオブジェクトは積極的にコレクションから削除しましょう。
- ガベージコレクションに時間がかかりすぎている場合
メモリの使用量を減らす
Javaでメモリを効率的に利用するには、まずヒープメモリの使用量を減らすべきです。ここでは、メモリの使用量を減らすための3つの方法(オブジェクトトのサイズを減らす、オブジェクトの初期化を遅らせる、canonicalオブジェクトの利用)を紹介します。
オブジェクトのサイズを減らす
オブジェクトはヒープ上の一定の領域を消費するので、メモリの使用量を減らすにはオブジェクトを小さくするのが一番の近道です。 オブジェクトのサイズを減らすには、インスタンス変数の数を減らすという方法とインスタンス変数のサイズを減らすという方法があります。nullのインスタンス変数も、一定のメモリを消費します。
オブジェクトの初期化を遅らせる
対象の処理の頻度が低い場合に、このように初期化を遅らせると効果的です。一方、処理が頻繁に行われる場合にはメモリが節約されず、パフォーマンスをはわずかに悪化する可能性があります。
事前の解放
変数の値としてnullをセットすると、ガベージコレクターがより早く街灯のオブジェクトを解放できます。しかし、これが効果的なケースは限られています。 ローカル変数は、メソッドの終了後に有効範囲から外れるため自動的にガベージコレクターによる解放の対象になります。
immutableオブジェクトとcanonicalオブジェクト
Javaでは多くの型のオブジェクトはimmutable(変更不能の意)です。Booleanクラスのコンストラクタはprivateなものだけにし、パラメータの値に応じてBoolean.TRUEとBoolean.FALSEのいずれかを返すようなstaticメソッドを用意するというのがより良い設計です。 このように、同じ意味を持つ複数のimmutableオブジェクトを代表した表現をcanonicalオブジェクトと呼びます。
canonicalオブジェクトの生成
Stringクラスで行われているように、オブジェクトをcanonical化することによって、同一のimmutableオブジェクトが繰り返し生成されるのを防ぎます。その結果、アプリケーションが使用するヒープの量を大幅に削減できます。