- - PR -
JSTLのメモリリーク?
投稿者 | 投稿内容 | ||||||||
---|---|---|---|---|---|---|---|---|---|
|
投稿日時: 2006-10-06 16:46
Strutsを利用したWebアプリを開発しているのですが、
Actionクラス内で作成したListオブジェクトを HttpRequest.setAttribute()でrequestオブジェクトに格納して 表示用JSPにforwardし、JSP内でJSTLの<forEach>タグを使用して Listオブジェクトの中身を表示するという処理を行っていました。 このActionとJSPはお互いにforwardし合うのですが、Listオブジェクトの サイズが大きいと、かならず二回目のAction -> JSPのforwardで OutOfMemoryErrorが発生しました。 色々と試してみて、JSTLの<forEach>タグではなくてStrutsの <logic:iterate>タグを使うとOutOfMemoryErrorは発生しなくなったのですが これはJSTLの<forEach>タグの実装がメモリリークを起こすような コードになっているんでしょうか? このような現象を経験された方いらっしゃいますでしょうか? | ||||||||
|
投稿日時: 2006-10-06 17:24
forwardし合うとはどういう意味ですか? Servlet側(StrutsのAction含む)からJSP側へのRequestDispatcher.forward()は解るとして。 JSPからServletにforwardというのはちょっとピンときません。 <jsp:forward>?いやいやそんな筈は… | ||||||||
|
投稿日時: 2006-10-06 17:42
とりあえず、以下のような検証を行いました。
1.java.lang.Object.finalize()をオーバーライドして ガーベッジコレクションに回収される際にSystem.outに ログを書き出すHogeクラスを作成 2.ListにHogeを格納してJSPにforward 3.JSPで該当Listを<forEach>でループさせる JSPの表示後にHogeクラスのfinalize()が 呼び出されることが確認できました。 通常のServletにての確認なので前提条件は異なるかもしれませんが JSTLの<forEach>が単純にオブジェクトをリークしていることは否定されたと思います。 なお、当方の環境はWindowsXP上でJDK 5。 Tomcat5.5 なので JSTL 2.0 の筈。 別バージョンは未検証です。 | ||||||||
|
投稿日時: 2006-10-06 17:44
nagise様
すみません、書き方が悪くて混乱させてしまいましたが、 単純にJSPのformからrequestを送信するだけです。 struts-config.xmlのAction-mappingではこのように定義しています。 <action-mappings> <action path="/memorytest" name="memorytestform" type="memorytest.action.MemoryTestAction"> <forward name="forward1" path="/pages/memorytest.jsp"/> </action> </action-mappings> 参考までに検証用に作ったActionとJSPのソースを載せておきます。 JVMのヒープサイズは256MBです。 =========== JSP ======================= <body> <html:form method="GET" action="/memorytest"> <input type="submit" /><br> <% if (null != request.getAttribute("resultList")) { %> <c:forEach var="listOfRows" varStatus="rowStatus" items="${resultList}"> ${listOfRows}<br> </c:forEach> <% } %> </html:form> </body> ====== Action ================ public class MemoryTestAction extends Action { public ActionForward execute(ActionMapping map, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { String var = "AAAAAAAAAAAAAAAAAAAAAAAAAAAA"; List<String> list = new ArrayList<String>(); try { for(int i = 0; i < 1300000; i ++) { String temp = new String(var.getBytes()) + i; list.add(temp); } } catch (OutOfMemoryError oome) { System.out.println("*********** OutOfMemoryの発生 ************"); oome.printStackTrace(); req.removeAttribute("resultList"); return map.findForward("forward1"); } req.setAttribute("resultList", list.subList(0, 300)); return map.findForward("forward1"); } } | ||||||||
|
投稿日時: 2006-10-06 18:54
ちょっとListに乗っけるインスタンスの量が多すぎませんかね?。 一時的に消費されるメモリ量を計算してみました。 JRE 1.4.2での場合ですが、Stringクラスのインスタンスのメモリ消費量は、 2 * (Length) + 38 ± 2 だそうなので、ループ後のlistインスタンスのサイズは 2 * (28) + 38 + 2 = 124800000byte ※+2で計算 124800000byte / 1024 / 1024 ≒ 119Mbyte 凄いサイズになってしまいました・・・。 あと、ArrayListの初期化なんですが、
要素数を指定しないと、必要になる度に領域を拡張して行くと思うので、 そういった内部処理でもメモリ消費が進んでいるかもしれませんね。 [ メッセージ編集済み 編集者: 小僧 編集日時 2006-10-06 18:58 ] | ||||||||
|
投稿日時: 2006-10-06 19:11
nagise様 検証ありがとうございます。 こちらの環境でも同様のMemTestListクラスを作成し、さらに Actionクラス内のfor文の前後にprint文を追加してListオブジェクトの 生成開始と完了を見てみました。 すると、<logic:iterate>タグを使用した場合は、 生成開始 -> finalize -> 生成完了 という順番でprint文が出力されていたのに対し、 <forEach>タグでは 生成開始のprint文が出力された後でfinalizeのprint文は出力されずに OutOfMemoryErrorが発生しました。 このことからやはり生成したListオブジェクトがメモリ上に残っているのだと 思うので、来週にでもStrutsを使わないケースでの検証などしてみようと思います。 P.S finalize()をオーバーライドしてオブジェクトが破棄されることを確認する 方法は気づきませんでした。大変勉強になりました。ありがとうございます。 | ||||||||
|
投稿日時: 2006-10-06 19:25
小僧様
ご意見ありがとうございます。 今回の検証ではListオブジェクト作成処理を2回繰り返すとJVMのヒープサイズを オーバーするという状況にしたかったのでListオブジェクトのサイズが大きくなるように しました (JVMのヒープサイズを小さくすれば良かったんですが、他の作業の 関係でJVMのヒープサイズを変えたくなかったもので(汗)) ArrayListの初期サイズの指定についても今後の検証で色々試してみたいと思います。 | ||||||||
|
投稿日時: 2006-10-06 19:36
でもコレ、確実ではないんですよ。 finalize()が呼ばれたことでガーベッジコレクションに回収されたとは 言えると思いますが、呼ばれないことでリークしているとはいえません。 通常、スコープをはずれて参照されなくなった時点で直ちにガーベッジコレクションが 回収するわけではないので、この間にタイムラグがあります。 finalize()がされるタイミングはVMのガーベッジコレクションの実装に依存しますから。 そして、必ず呼ばれるという保証もない(System.exit()とかすると呼ばれずに終了します)。 今回のサンプルのように単に1発どでかい参照を用意するというケースでは リークしていようがいまいが、その時点でヒープ領域が埋まればOutOfMemoryですからね。 「リークしている」と言うためには「本来の役目が終わった後にも参照を保持しつづけている」 ということを証明しないといけません。 ほどほどのメモリを食うオブジェクトをforEachするコードを用意して それを何度か繰り返し呼び出した際にOutOfMemoryが発生するのであれば forEachタグがオブジェクトをリークしている疑いがでてきますが、 その検証コードでは、検証コードそのものが誤っている疑いが強いと思いますよ。 |