職場では、いくつかの webapps を実行するいくつかの tomcat サーバーがあり、その約半分は画像処理を行う必要があります。
画像処理を行う前に、これらの Web アプリケーションはImageIO.scanForPlugins()
、適切な画像リーダーとライターをメモリに取り込むために a を実行します。これまではイメージの処理が必要なときにいつでも実行されていましたが、現在は webapps が初期化されている場合にのみスキャンを実行します (実行後に jar を追加しないため、スキャンを複数回実行する必要はありません)。
数日後、Tomcat インスタンスが原因でクラッシュしましたOutOfMemoryError
。幸いにもHeapDumpOnOutOfMemoryError
オプションが設定されていたので、ヒープ ダンプを調べました。ダンプでは、メモリの 97% が のインスタンスによって使用されていることがわかりましたjavax.imageio.spi.PartialOrderIterator
。java.util.LinkedList
そのスペースのほとんどは、 1,800 万の要素を持つバッキングによって占められていました。リンクされたリストはjavax.imageio.spi.DigraphNode
、によってロードされたイメージ リーダーとライターを含む で構成されImageIO.scanForPlugins()
ます。
「あはは」、「どこかでループ内でスキャンを実行しているに違いなく、同じ要素を何度も何度も追加しているだけだ」と思いました。しかし、この仮定を再確認する必要があると考えたので、次のテスト クラスを作成しました。
import javax.imageio.ImageIO;
public class ImageIOTesting {
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
ImageIO.scanForPlugins();
if (i % 1000 == 0) {
System.out.println(Runtime.getRuntime().totalMemory() / 1024);
}
}
}
}
しかし、このクラスをサーバー環境で実行すると、メモリの使用量はまったく変わりません。
javax.imageio パッケージのソースをざっと調べてみると、サービス プロバイダーが既に登録されているかどうかをスキャンがチェックし、登録されている場合は新しいプロバイダーを登録する前に古いプロバイダーの登録を解除することがわかります。ここで問題となるのは、なぜこの巨大なサービス プロバイダーのリンク リストがあるのかということです。なぜそれらは有向グラフとして保存されるのですか? さらに重要なことに、これが起こらないようにするにはどうすればよいですか?