- PR -

ImageIOからのnative methodでOutOfMemory?

1
投稿者投稿内容
よし
会議室デビュー日: 2005/02/01
投稿数: 4
投稿日時: 2005-02-01 01:22
いつも勉強させていただいております。

初めて投稿させていただきますが、不備な点がありましたら
ご指摘ください。

現在、StrutsのFileUploadを使用して、送られてきた画像を
Servlet側でImageIO.read(InputStream)を使用して読み込み
ローカルディスクに保存する処理を実装し、すでに稼動しているのですが
約3日(使用頻度による)OutOfMemoryErrorが発生してしまい対応に苦慮しております。

始めはヒープ領域の設定かと思いましたが、-Xmx1024m -Xmx1024mで
設定をしており、物理メモリについても1.5G程の空きがあるため
特に問題は無いと考えております。

また、下記のようにOutOfMemoryErrorは表示されているのですが
Webアプリケーションは正常に動作しており、画像処理を行う部分
についてのみ処理が行えない状況です。
#結局、APサーバ再起動を行うことになるのですが…

ただ、Windowsのタスクマネジャーを確認してみると画像処理の
Actionが実行されるたびにメモリの使用量は増加しています。

スタックトレースからinitJPEGImageReaderの処理を
確認してみましたが、nativeのコールを行っているようで
いったい何が悪いのかポインターさえ無い状況です。


何か少しでも情報をいただければ幸いです。
よろしくお願いいたします。

---------
java.lang.OutOfMemoryError: Initializing Reader
at com.sun.imageio.plugins.jpeg.JPEGImageReader.initJPEGImageReader(Native Method)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.<init>(JPEGImageReader.java:233)
at com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi.createReaderInstance(JPEGImageReaderSpi.java:89)
at javax.imageio.spi.ImageReaderSpi.createReaderInstance(ImageReaderSpi.java:296)
at javax.imageio.ImageIO$ImageReaderIterator.next(ImageIO.java:488)
at javax.imageio.ImageIO.read(ImageIO.java:1381)
at javax.imageio.ImageIO.read(ImageIO.java:1306)
----- 以下略
シュン
ぬし
会議室デビュー日: 2004/01/06
投稿数: 328
お住まい・勤務地: 東京都
投稿日時: 2005-02-01 11:16
ImageIO#read()コールより受け取った画像データは、「画像」の
状態で一旦オンメモリに全て展開されてしまうことになります。
「画像」の展開を行うのに十分なメモリが確保できそうになけ
れば、その時点でOutOfMemoryErrorが発生することはありえます。

ファイルアップロードの処理が単純にアップロード->サーバーのロー
カルストレージに保存という処理だけならば、ImageIOを使用せずに
単純なStreamを使用してバケツリレーをすることで、メモリ使用量
を減らすことができると思いますよ。


InputStream in = マルチパートリクエストのJPEG画像パート;
FileOutputStream out = new FileOutputStream("保存するファイル名");
byte[] buf = new byte[バケツリレー用バッファサイズ];
繰り返し(inがJPEG画像パート終端に到達するまで){
 int size = in.read(buf);
out.write(buf,0,size);
}

#知ったかぶりでした。ごめんなさい。ハズカシ

[ メッセージ編集済み 編集者: シュン 編集日時 2005-02-01 15:23 ]
未記入
ぬし
会議室デビュー日: 2004/09/17
投稿数: 667
投稿日時: 2005-02-01 12:38
えーと、確かバグパレに出ていたと思うけど、ImageIO API って、スタティックメソッドを使用するとメモリリークするんですよ。内部で毎回、ImageReader インスタンスを生成しているんだけど、うまく後処理が出来ていないらしくインスタンスが破棄されない模様。

なので、スタティックメソッド ImageIO#read() を使わずに、自分で ImageReader インスタンスを生成して、それを使いまわすようにしてください。
コード:
    ImageReader jpegReader = (ImageReader)ImageIO.getImageReadersByFormatName("jpeg").next()



ファイルに、まったく同じ状態で保存するのであれば、シュンさんの言うように ImageIO を使う必要はなく、受けたストリームをそのままファイルに書いていけばいいだけですね。
よし
会議室デビュー日: 2005/02/01
投稿数: 4
投稿日時: 2005-02-01 15:56
>ImageIO#read()コールより受け取った画像データは、「画像」の
>状態で一旦オンメモリに全て展開されてしまうことになります。
>「画像」の展開を行うのに十分なメモリが確保できそうになけ
>れば、その時点でOutOfMemoryErrorが発生することはありえます。

その時点でOutOfMemoryErrorというのは、JVMのヒープとは関係の無いところ
なんでしょうか?GCのログを見ると確保したサイズ(現在は512M)の1/10も
使用していません。
また、画像のImageIOを使用した表示以外の部分については、問題なく
稼働しているというところが、私を混乱させます。

>ファイルアップロードの処理が単純にアップロード->サーバーのロー
>カルストレージに保存という処理だけならば、ImageIOを使用せずに
>単純なStreamを使用してバケツリレーをすることで、メモリ使用量
>を減らすことができると思いますよ。

コードについては、略させていただきますが、これについては
別途、勉強させていただきます。
ImageIOを使用する経緯は
ファイルアップロード->画像表示->(画面からの操作で)GIFとの合成
ということを実装するためでした。
InputStreamからの読み込みは、FormFileにgetInputStreamがあるし
ImageIOにはInputStreamから簡単に読み込みが出来そうだしという
安直な考えからです。

>#知ったかぶりでした。ごめんなさい。ハズカシ

いや、かなり助かりますし、勉強になります。

>えーと、確かバグパレに出ていたと思うけど、ImageIO API って、スタティックメソッドを使用するとメモリリークするんですよ。
>内部で毎回、ImageReader インスタンスを生成しているんだけど、うまく後処理が出来ていないらしくインスタンスが破棄されない模様。
>なので、スタティックメソッド ImageIO#read() を使わずに、自分で ImageReader インスタンスを生成して、それを使いまわすようにしてください。

上記のアドバイスを元にして、以下のように改造してみました。
一度本番環境に適用して、結果報告が出来ればと思います。

---------------------------------------------------------------------
ImageReader jpegReader = (ImageReader)ImageIO.getImageReadersByFormatName("jpeg").next();
jpegReader.setInput(ImageIO.createImageInputStream(is));

BufferedImage bi = jpegReader.read(0);
jpegReader.reset();
jpegReader.dispose();
---------------------------------------------------------------------
不勉強を承知で確認させていただきたいのですが、ImageIOが持ってるメソッドは、すべてstaticに見えます。
ということは、絶対にメモリリークするということなのでしょうか?
よし
会議室デビュー日: 2005/02/01
投稿数: 4
投稿日時: 2005-02-03 22:43
結果が出ました。

ImageReaderを使用してもJVMの領域が改善されましたが
Windows上のメモリについては、増加傾向が止まりませんでした。
(正確にはjrun.exeの使用メモリが増加していました。)

という事で、javadocを再度見てみるとImageIOはメモリまたはディスクに
対して、キャッシュする設定がデフォルトTRUEのようで、以下のロジックを
追加することで、メモリの増加傾向が止まりました。
--------------------------
setUseCache(false)
--------------------------

今回のシステムでは、色々なところから色々な写真をアップロード
してくるということで、キャッシュの必要も無いため、問題がありません。
これで運用していこうと思います。

今回の現象としては、jrun.exeのメモリが増えすぎてしまい
画像読込用のnativeメソッドがメモリの確保が出来なくなったと
考えています。(と信じているが正しい?)
未記入
ぬし
会議室デビュー日: 2004/09/17
投稿数: 667
投稿日時: 2005-02-03 23:35
引用:

javadocを再度見てみるとImageIOはメモリまたはディスクに対して、キャッシュする設定



あれ? そうなの? ImageIO#setUseCache() はキャッシュにディスクを使用するかどうかの設定で、メモリへのキャッシュを抑制することまではできないと思っていたけど。javadoc でも「ディスクベースのキャッシュファイルを使用する必要があるかどうかを示すフラグ」 と説明されている。

メモリキャッシュまで抑制するためには、ImageInputStream のサブクラスを自分で書く必要があるような気がする。
よし
会議室デビュー日: 2005/02/01
投稿数: 4
投稿日時: 2005-02-04 01:10
あまりに劇的な変化もので、浮かれて書き込みしたみたいです…

ご指摘の通り
「ディスクベースのキャッシュファイルを使用する必要があるかどうかを示すフラグ」
でした…あまりにメモリの増加量が少なかったので、これが効いたとばかり思っていました。

明日、もう一度確認してみます。

1

スキルアップ/キャリアアップ(JOB@IT)