オブジェクトをシリアライズすると、ガベージコレクタに回収されず、生存期間が延長されることがある。ObjectOutputStream は、シリアライズする各オブジェクトの参照(あるいはハンドル)を管理し、各オブジェクトがストリームに1回だけ書き込まれるようにしている。既にストリームに書き込まれたオブジェクトが再度書き込まれる場合には、最初に書き込まれたオブジェクトへの参照に置き換えられる。最初に書き込まれた時点と再度書き込まれる時点の間にオブジェクトの状態が変更されていても、この置き換えが行われることに注意。この置き換えを行うためには、シリアライズしたオブジェクトへの参照を管理する一覧表が必要である。ガベージコレクタはよそから参照されているオブジェクトインスタンスを回収することはできないため、参照の一覧表が存在するかぎり、シリアライズしたオブジェクトはガベージコレクタに回収されない。
グラフ構造をなすオブジェクトの集合に対してこの振舞いは望ましいものであり、また正しいものである。とくに、シリアライズが行われる前に、グラフ構造のメモリ上への割り当てと構築が完了している場合であればなおさらそうである。しかし、シリアライズする他のオブジェクトへの参照を持たず、シリアライズの処理が始まってからメモリ割り当てが行われるようなデータの場合には、この動作はメモリ枯渇を招く危険がある。たとえば、外部センサからのデータをシリアライズする場合などが該当する。そのような場合、メモリ枯渇を防ぐために、追加の処理を行う必要がある。すなわち、複数の独立したデータを読み込みシリアライズするプログラムでは、1回の読み込みごとに参照の一覧表をリセットする必要がある。
このルールは、「MSC05-J. ヒープメモリを使い果たさない」の具体例になっている。
違反コード
以下の違反コード例では、外部センサからデータを読み込みシリアライズしている。readSensorData() メソッドを一回呼び出すと、1MBのデータを持った SensorData のインスタンスが新たに生成される。SensorData インスタンスは、他の SensorData インスタンスへの参照を持たないデータの並びである。
既に説明したように、ObjectOutputStream はシリアライズしたオブジェクトのキャッシュを管理している。キャッシュデータがガベージコレクタに回収されるまで、すべての SensorData オブジェクトはメモリ上に残っている。シリアライズデータを書き込んでいる間はストリームはオープンされたままなので、OutOfMemoryError が発生する可能性がある。
class SensorData implements Serializable { // インスタンスごとに 1 MB のデータ! ... public static SensorData readSensorData() {...} public static boolean isAvailable() {...} } class SerializeSensorData { public static void main(String[] args) throws IOException { ObjectOutputStream out = null; try { out = new ObjectOutputStream( new BufferedOutputStream(new FileOutputStream("ser.dat"))); while (SensorData.isAvailable()) { // 各 SensorData オブジェクトのサイズは 1 MB SensorData sd = SensorData.readSensorData(); out.writeObject(sd); } } finally { if (out != null) { out.close(); } } } }
適合コード
以下の適合コードでは、各 SensorData オブジェクトが他の SensorData オブジェクトへの参照を持っていないという性質を生かし、個々の書き込みごとにストリームをリセットしている。リセットにより、内部のオブジェクトキャッシュはクリアされる。つまり、ストリームに書き込まれた SensorData オブジェクトへの参照の管理データがクリアされ、どこからも参照されなくなった SensorData オブジェクトはガベージコレクタによって回収される。
class SerializeSensorData { public static void main(String[] args) throws IOException { ObjectOutputStream out = null; try { out = new ObjectOutputStream( new BufferedOutputStream(new FileOutputStream("ser.dat"))); while (SensorData.isAvailable()) { // 各 SensorData オブジェクトのサイズは 1 MB SensorData sd = SensorData.readSensorData(); out.writeObject(sd); out.reset(); // ストリームをリセット } } finally { if (out != null) { out.close(); } } } }
リスク評価
シリアライズの過程でメモリリークやリソースリークが発生する場合、リソースを枯渇させる攻撃を受けたり、JVM がクラッシュする危険がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
SER10-J | 低 | 低 | 低 | P3 | L3 |
関連ガイドライン
MITRE CWE | CWE-400. Uncontrolled resource consumption (aka "resource exhaustion") |
CWE-770. Allocation of resources without limits or throttling |
参考文献
[API 2006] | |
[Harold 2006] | 13.4, Performance |
[Sun 2006] | Serialization Specification |
翻訳元
これは以下のページを翻訳したものです。
SER10-J. Avoid memory and resource leaks during serialization (revision 61)