- - PR -
メモリリークの発生箇所とその対策について
投稿者 | 投稿内容 | ||||
---|---|---|---|---|---|
|
投稿日時: 2006-12-05 18:48
いつもお世話になっております。
今回のプロジェクトにおいて下記本番(開発)環境にてWEBアプリケーションを構築しております。 --------------------------------------------------------------- 【現在本番(開発)環境】 OS : Windows 2003 サーバ (開発PCは、WindowsXP sp2 とWindows2000 sp4) JAVA : jdk-1_5_0_06 apache : 2.0.55 tomcat : 5.5.16 Eclipse : All-In-One-Eclipse-2.0.0 Oracle 9.2 --------------------------------------------------------------- 現在プロジェクトの終盤を迎えているのですが、ここにきて ---------------------------------------------------------------------------- 致命的: サーブレット jsp のServlet.service()が例外を投げました java.lang.OutOfMemoryError: Java heap space 2006/11/29 15:19:51 org.apache.catalina.core.StandardWrapperValve invoke ---------------------------------------------------------------------------- 悩まされ、フリーのプロファイリングソフトにてモニタリングを開始しました。 その結果、メモリリークを発生させている箇所が2つあることがわかりました。 (1) データベースをアクセスする際のSQL文を StringBuffer sql = new StringBuffer(); に作成し、SQL文発行後にクリアしていなかった。 (抽出条件複雑であり、且つ出力項目が多い為、StringBuffer()に作成。) (2)今回、「xxx別一覧画面」を一旦、HashMap に格納し一覧表示しているが 一覧を表示後、クリアしていなかった。 /** 営業担当者データ一覧 */ private ArrayList<HashMap> EgyouTanList = null; /** 設計受付者データ一覧 */ private ArrayList<HashMap> SekkeiUkeList = null; この2点について、これから対策案を練るのですが、その前に有識者の皆さんにご意見を頂ければ思い、投稿させて頂きました。 このようなケースの場合、皆さんはどのような対策を立ててますか(立てますか?)ご意見をお聞かせください。 | ||||
|
投稿日時: 2006-12-05 19:04
ぱっと聞いた感じでは、ローカル変数で処理していれば
オブジェクトがガーベッジコレクションの回収対象にならない なんてことは起き得ないところなのですが…。 Servlet(およびフレームワークなどでそれに準ずる位置づけ)のクラスで フィールド変数に格納しているのでしょうか。 Servletの具象クラスは(設定にもよりますが)通常はWebアプリケーションの 起動中はインスタンスが保持され続けるため、特別な事情がない限り フィールド変数を用いないのがServletを用いたシステムでは常識です。 うっかり使用すると同時に複数のリクエストが走った場合に マルチスレッド特有の再現性の低いバグを発生させます。 もし、上記のようなケースなのだとしたら、失礼ながらソースの品質は 決してよいとは言えない状態と推察されます。 ある程度の技術者(マルチスレッドにおける注意点を把握している程度)に 全面的にソースレヴューしてもらうなり、FindBugsなどの チェッカーで機械的に危険箇所を洗うなりして 品質向上に努めるより仕方ないのではないでしょうか。 目先の該当箇所の修正という話でしたら、ローカル変数で済むものは ローカル変数で扱いましょう、という話でしょう。 | ||||
|
投稿日時: 2006-12-05 20:10
nagiseさん、貴重なアドバイスありがとうございます。
是非参考にさせていただきます。 尚、今回 「java.lang.OutOfMemoryError」 について調べたところ JAVA Pressに ・StringBufferクラスは、大きなバッファを確保したら、それ以降の処理で縮小することありません。(Vol.32 号) ・Javaのプログラムでメモリリークを起こしやすいのは、java.util.HashMap のようなMapコレクションクラスです。 Mapには、自由にデータを出し入れできるのですが、不要になったデータを削除することを忘れてしまい、リークオブジェクトが溜まってしまいます。(Vol.47 号) にきちんと記載してありました。(勉強不足ですいません。) 開発時点では各自のPCで Tomcat を何度も立ち上げたり切ったりするので気づきませんでしたが、終盤にきて本番機にて一斉にテストしたことで発覚したのが現状です。 (本番前に気づいただけでもラッキーですかね。) 尚、後日対策結果を報告します。 | ||||
|
投稿日時: 2006-12-06 00:13
OutOfMemoryErrorの発生原因で一番可能性が高いのは、
メモリリークよりも巨大な連続領域の確保ではないでしょうか。 それなりなサイズだとしても、SQL文程度ではOutOfMemoryErrorに なるまでには相当な回数が実行される必要があります。 見たことのあるパターンでは、数千〜数万件のデータを格納したXMLを あらかじめStringBufferで完全に連結してから解析した場合でした。 StringBufferで単純に組み立ていると、 平均的にUCS-2の方が一文字単位の容量は大きいので、元のサイズ以上の容量を、 最後の結合時はバッファ拡張時のコピー元とコピー先で二つの連続メモリ領域 として必要とすることになります。 現状のJVMは配列での領域分割は行わないようなので、断片化したヒープ領域から 数MB単位の連続領域を確保することは非常に大きなストレスになってしまいます。 | ||||
|
投稿日時: 2006-12-06 21:52
いろいろソースを見直してはいるのですが、苦戦しております。
あしゅさん、巨大な連続領域の確保を疑うとしたら、どの部分を重点にみるといいですか?VM起動時の設定も疑ったほうがいいですか?ちなみにVM起動時の設定は何もしておりません。 | ||||
|
投稿日時: 2006-12-06 22:12
つぎのいずれかにします。 (1) 今のソースコードをぜんぶ捨てて、0から作り直す。 (2) メモリー消費量を計測して、永遠に増えつづけるのか、それとも有限なのかを見極める。 http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/lang/Runtime.html の freeMemory() maxMemory() totalMemory() の3つ(とくに freeMemory())を随時、取得して、これがどう変化するかを見ます。 もしもどこかで頭打ちになるか、途中で OutOfMemoryException になるものの、頭打ちになるような気配があるようならば、そのアプリケーションがメモリーを大食いなだけで、必ずしも悪いというわけではないです。メモリーを節約するようなコーディングにするか、起動時のメモリー設定で多めの値を指定すれば解消できることが多いでしょう。 もしも増え続けるのならば、たとえば List にずっと add しっぱなしで忘れているなどです。これは致命的なバグなので、それを直さない限りどうしようもありません。 -- unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86} | ||||
|
投稿日時: 2006-12-07 06:39
unibonさん、貴重なご意見ありがとうございます。
(1) 今のソースコードをぜんぶ捨てて、0から作り直す。 は、作成メンバが同じですので、解決策を明確にしない限り同じてつをふむことになります。まずは(2)で見極めていくこととします。 >たとえば List にずっと add しっぱなしで忘れているなどです も体力勝負で見直します。ありがとうございました。 今後「java.lang.OutOfMemoryError」 で検索する方の為、 List に add したら xxx をする。(by unibonさん) みたいに、メモリ節約のつぼを書き記しておくとプログラミングの向上に一役かうのかもしれませんね。せっかくですのでそのようなご意見ありましたらどんどん書き込んでください。宜しくお願いします。 | ||||
|
投稿日時: 2006-12-07 18:44
お世話様です。
現在、ヒープ領域(メモリ)を64→128に拡張し耐久テストを実施しております。これで一旦様子をみます。尚、モニタリングできるよう、簡単なサーブレットを作成しました。参考になれば幸いです。後日最終結果を報告します。 ---------------------------------------------- /* * クラス名 : Jmast_MemoryMonitor2.java * * 機能 : メモリ利用状況確認 * * バージョン情報 : ver1 * * 作成日付 : 2006/12/07 * * 作成者 : nob * */ package ks_eco.master; /* -------------------------------- * パッケージ定義 --------------------------------*/ import java.io.*; import java.lang.management.*; import javax.servlet.*; import javax.servlet.http.*; //*-------------------------------------------------------------------- //* プログラム開始の宣言(本プログラム名称を宣言する) //*-------------------------------------------------------------------- public class Jmast_MemoryMonitor2 extends HttpServlet { public void doGet(HttpServletRequest req,HttpServletResponse res) throws ServletException, IOException { //*-------------------------------------------------------------------- //* 初期設定 //*-------------------------------------------------------------------- PrintWriter out; res.setContentType( "text/html;charset=Shift_JIS" ); out = res.getWriter(); //*------------------------------------------------------------------- //* //* メイン処理 //* //*------------------------------------------------------------------- try { //*------------------------------------------------------------------- //* //* 1. メモリ管理インターフェース取得 //* //*------------------------------------------------------------------- MemoryMXBean mbean = ManagementFactory.getMemoryMXBean(); //*------------------------------------------------------------------- //* //* 2. ヒープメモリ使用状況取得 //* //*------------------------------------------------------------------- MemoryUsage heap = mbean.getHeapMemoryUsage(); MemoryUsage nonheap = mbean.getNonHeapMemoryUsage(); int numFinalizers = mbean.getObjectPendingFinalizationCount(); //*------------------------------------------------------------------- //* //* 3. ヒープメモリ使用状況表示 //* //*------------------------------------------------------------------- out.println("<html>"); out.println("<head>"); out.println("<META HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=x-SJIS\">"); out.println("<title>電器Web</title>"); out.println("</head>"); out.println("<script language='JavaScript'>"); out.println("<!--"); out.println("function goFunc()"); out.println("{"); out.println(" document.fm2.action = '../../ks-eco/servlet/ks_eco.master.Jmast_MemoryMonitor2';"); out.println(" document.fm2.target = '_self';"); out.println(" document.fm2.submit();"); out.println("}"); out.println("// -->"); out.println("</script>"); out.println("<body onload=\"setInterval('goFunc()',5 * 1000);\">"); out.println("<form name='fm2' action='../../ks-eco/servlet/ks_eco.master.Jmast_MemoryMonitor2' target='_self' method='get'>"); out.println("<input type='submit' value='Submit'>"); out.println("</form>"); out.println("<h3 class=posi style = \"font-size: 12px\"><span class=title>-------------------------------------------------------------</span></h3>"); out.println("<h3 class=posi style = \"font-size: 12px\"><span class=title>【ヒープメモリ使用状況表示】</span></h3>"); out.println(" <table>"); out.println(" <tr><td>heap</tb><td> used : " + heap.getUsed() + "</tb><td> max : " + heap.getMax() + "</tb></tr>"); out.println(" <tr><td>nonheap</tb><td> used : " + nonheap.getUsed() + "</tb><td> max : " + nonheap.getMax() + "</tb></tr>"); out.println(" <tr><td>Finalizers</tb><td>" + numFinalizers + "</tb><td> </tb></tr>"); out.println(" </table>"); out.println("<h3 class=posi style = \"font-size: 12px\"><span class=title>-------------------------------------------------------------</span></h3>"); } catch (Exception e) { out.println("<br><hr><font color=red class=\"specal2\">◆処理中にエラーが発生しました。<br> エラーメッセージ= " + e); out.println("</font>"); } finally { out.println("</body>"); out.println("</html>"); out.close(); } } } |