Deflate操作時に使用するjavaの標準ライブラリであるjava.util.zip.Deflaterとjava.util.zip.InflaterはJNIで単にzlibを呼んでいるだけなので(少なくともOracle JRE/OpenJDKの場合)、newのタイミングでJVMの管理外の領域に少なくとも辞書窓サイズ分のメモリを確保する。Deflateの辞書窓サイズはだいたい32KiBぐらいある。
次のコードはJVMには大した影響を与えないが、あっさりJVMの外のシステムのメモリを枯渇させるだろう。
public class DeflateSample {
public static void main(String... args) {
for (;;) {
new java.util.zip.Deflater();
}
}
}
##対応
使い捨てでDeflater/Inflaterを使う場合は必ずend()
を呼ぶ。または使い捨てず、可能な限りreset()
して使いまわすようにする。
finalize()
がend()
を呼び出しているので、GCが走れば解決はする。したがってend()
しなかったとしても完全なメモリリークにはならない。
しかしDeflaterがJVM内部で必要とするメモリサイズに比べ、JVM外部で消費するメモリサイズのほうが圧倒的に大きいので、システムメモリが枯渇するより先にJVMのメモリが尽きてGCが走るだろうと思うのは幻想である。
なお、GZIPOutputStreamとGZIPInputStreamは内部でDeflater/Inflaterを生成しており、close()
するまでend()
を呼ばないので、必ずclose()
しよう。
###おまけ
恐ろしいことにJavadoc上のend()
の重要性はかなり曖昧だ。「実装の問題」ということなんだろうか。おかげでこの問題は自力で引き当てるまでなかなか気が付かない。