- PR -

アプレットにて使用メモリが増え続ける

投稿者投稿内容
会議室デビュー日: 2004/08/20
投稿数: 16
投稿日時: 2006-04-11 17:49
お世話になります。

IE上で、定周期的にデータを取得するアプレットが組み込まれているページを開いていると、徐々にIEのプロセスメモリが増えてくるという現象に悩んでいます。
原因がまったくつかめずに困っています。
どなたか原因がお分かりになられる方がいらっしゃいましたら、ご教授お願いいたします。

以下、システムの内容です。
業務上作成しているシステムですので、申し訳ございませんが、ソースのほとんどを公開することは出来ません。
情報量が少なくなってしまい、申し訳ございませんが、何か解決につながるヒントでも結構ですので、教えていただければ幸いです。


〜概要〜
現在、アプレットを用い、サーバーから定周期でデータを取得して、アプレットの呼び出し元HTML中のJavaScriptと通信を行い、画面更新を行うシステムを作成しております。
定周期更新は10秒ごとに行っております。

〜現象〜
体感で3分置きに4KBメモリが増加します。
JavaScriptとの切り分けのために、JavaScriptと通信を行っている部分をコメントアウトしても、結果は変わりませんでした。
スレッドを定期的にダンプしてみても、スレッドの数等、内容に変化はありませんでした。
zombieになっているクラスもありません。

〜環境〜
OS:アプレット構築・動作共に WindowsXP Pro.
ブラウザ:IE6.0SP1
JRE:SunJava jre-1.5.0_06
JDK:SunJava jdk-1.5.0_06

アプレットのソース抜粋です。
public class header extends Applet implements Runnable {
  //この部分でメンバを定義しています
  int Ntime;//周期時間
  Thread th;//定周期更新用スレッド
  JSObject win;
  AudioClip BEEP;
  …
  
  //以下、メソッドの定義です。
  //アプレット固有の関数start
  public void start() {
    th = new Thread(this);
    th.start();
  }
  
  //アプレット固有の関数init
  public void init() {
    //メンバの初期化を行っています
    Ntime = 10000;//定周期更新時間10秒
    try {
      JSObject win = JSObject.getWindow(this);
    } catch (JSException eJS) {
      eJS.printStackTrace();
    }
    BEEP = getAudioClip(getCodeBase(), "dummy.wav");
    …
  }
  
  //JavaScriptと通信を行って、画面更新(画像の変更)を行う部分です
  public void WindowPaint(){
    win.call("HogeHoge", hoge);//JavaScriptとの通信です。(hogeはStringクラスの配列です)
    …
  }
  
  //サウンドを鳴らす部分です
  public void WindowSound(){
    if (BeepFlag == 1) {
      BEEP.loop();
    } else {
      BEEP.stop()
    }
    …
  }
  
  //データを取得する部分です
  public void DataGet() {
    InputStream in = null;
    BufferedReader dis = null;
    URL url;
    URLConnection urlConn;
    try {
      url = new URL(getDocumentBase(), urlcgi);//urlcgiデータ取得ようCGIのURLです。
      urlConn = url.openConnection();
      in = urlConn.getInputStream();
      dis = new BufferedReader(new InputStreamReader(in));
      String s;
      NewTime = rightNow.getTimeInMillis() ;
      interval1 = (int)((NewTime - OldTime) * 100) / Ntime;
      while ((s = dis.readLine()) != null) {
        StringTokenizer strToken = new StringTokenizer(s, "=");
        String Token = strToken.nextToken().toString();
        if (Token.equals("DATE")) {
          …
        }
      }
    } catch (Exception e) {
      …
    }
  }
  
  //定周期を行うrunメソッドです。
  public void run() {
    do{
      System.gc();//明示的にガベージコレクトを入れています
      DataGet();//各種データゲット
      WindowPaint();//画面更新
      WindowSound();//サウンド
      th.sleep(Ntime);
    } while (isActive ());
  }
}

長くなってしまい、申し訳ございませんが、よろしくお願いいたします。
uk
ぬし
会議室デビュー日: 2003/05/20
投稿数: 1155
お住まい・勤務地: 東京都
投稿日時: 2006-04-11 20:03
このコードでは省略されていてわかりませんが、URLConnection#getInputStreamで取得した
ストリームをクローズしてますか?
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2006-04-11 20:37
この手の問題としては、消費メモリーがひたすら増加していつかはメモリー不足に陥ってしまうパターンと、消費メモリーが頭打ちになりいつになってもメモリー不足にならないパターンに分かれます。前者なら大問題ですが、後者ならばメモリー管理がたまたまそういうふうなメモリーの使い方をするだけなのであまり気にしなくてよいでしょう(仕組みを知っておくことが良いのはもちろんですが)。
ただ、後者でも、ガーベッジコレクションが起こるまでリソースを開放していないのは、問題ですので、もしこうなっていたらそれは修正する必要はあります(実用上は問題はないでしょうが)。コネクションやファイルの close し忘れがこれにあたります。

また、メモリーを使っているのが Java Applet の側なのか IE の JavaScript の側なのかの切り分けをなんらかの方法でされたほうが良いでしょう。Java Applet の側ならば、プログラム中で
http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/lang/Runtime.html
の中の 〜Memory というメソッドで取得できます。
会議室デビュー日: 2004/08/20
投稿数: 16
投稿日時: 2006-04-12 10:53
引用:

ukさんの書き込み (2006-04-11 20:03) より:
このコードでは省略されていてわかりませんが、URLConnection#getInputStreamで取得した
ストリームをクローズしてますか?


uk様、ご回答ありがとうございます。
ご回答にございます、ストリームのクローズに関してですが、投稿させて頂きましたソースを省略しすぎていました。
実際のデータ取得関数は、以下のようになっております。

  //データを取得する部分です
  public void DataGet() {
    InputStream in = null;
    BufferedReader dis = null;
    URL url;
    URLConnection urlConn;
    try {
      url = new URL(getDocumentBase(), urlcgi);//urlcgiデータ取得ようCGIのURLです。
      urlConn = url.openConnection();
      in = urlConn.getInputStream();
      dis = new BufferedReader(new InputStreamReader(in));
      String s;
      NewTime = rightNow.getTimeInMillis() ;
      interval1 = (int)((NewTime - OldTime) * 100) / Ntime;
      while ((s = dis.readLine()) != null) {
        StringTokenizer strToken = new StringTokenizer(s, "=");
        String Token = strToken.nextToken().toString();
        if (Token.equals("DATE")) {
          …
        }
      }
    } catch (Exception e) {
      …
    } finally {
      try{
        if (in != null) {
          in.close();
        }
        if (dis != null) {
          dis.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

情報量不足、申し訳ございません。
定周期更新は10秒に1回行っていまして、ストリームのオープン、クローズに関するメモリの増減は正しく行われているように感じます。



引用:

unibonさんの書き込み (2006-04-11 20:03) より:
この手の問題としては、消費メモリーがひたすら増加していつかはメモリー不足に陥ってしまうパターンと、消費メモリーが頭打ちになりいつになってもメモリー不足にならないパターンに分かれます。前者なら大問題ですが、後者ならばメモリー管理がたまたまそういうふうなメモリーの使い方をするだけなのであまり気にしなくてよいでしょう

メモリーを使っているのが Java Applet の側なのか IE の JavaScript の側なのかの切り分けをなんらかの方法でされたほうが良いでしょう。


unibon様、ご回答ありがとうございます。
抱えています問題は、メモリー不足まで陥ってしまいます。
最終的にIEのプロセスが切られる現象が起こります。

Applet と JavaScript の切り分けとしまして、JSObjectクラスのcallメソッド全てをコメントアウトし、JavaScriptの関数をコールしない状況でも、やはり同じ現象が現れるようです。

Applet側のメモリを、ご紹介いただいたメソッドで取得するテストを行ってみたいと思いますので、結果が出たらまたご報告させていただきます。

ありがとうございました。
会議室デビュー日: 2004/08/20
投稿数: 16
投稿日時: 2006-04-14 12:42
Runtimeのメモリを監視した結果をご報告いたします。

結果から申し上げますと、Javaのコードに問題はございませんでした。
どうやら、JavaScriptに問題があるようです。
まだ解決はしていませんが、JavaScriptをつついていこうと思います。
uk様、unibon様、また思案してくださった方、ありがとうございました。


以下、今回行ったテスト内容です。
〜内容〜
runメソッドにて10秒更新を行うたび、
Runtime.getRuntime().totalMemory();
Runtime.getRuntime().maxMemory();
Runtime.getRuntime().freeMemory();
でメモリ情報を取得し、apletのrepaint()で画面に表示しました。

〜結果〜
freeMemoryは一定の増減を行いますが、減っていくようなことはありませんでした。
maxMemory,totalMemory共に変化はございませんでした。
会議室デビュー日: 2004/08/20
投稿数: 16
投稿日時: 2006-04-21 17:52
お世話になっております。
さらに調査を進めていくうちに、分からない現象が出てきましたので、原因と対処法をご存知の方がいらっしゃいましたら、ご教授をお願いいたします。

〜現象〜
パフォーマンスモニタにて、IEのハンドルカウンタを監視してみたところ、以下のコードにて、ハンドルを解放していない現象が起こりました。
ただし、JAVAのメモリ管理(GC)はうまく言っているようです。

  //データを取得する部分です
  public void DataGet() {
    InputStream in = null;
    BufferedReader dis = null;
    URL url;
    URLConnection urlConn;
    try {
      url = new URL(getDocumentBase(), urlcgi);//urlcgiデータ取得ようCGIのURLです。
      urlConn = url.openConnection();
      in = urlConn.getInputStream();
      dis = new BufferedReader(new InputStreamReader(in));
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try{
        if (in != null) {
          in.close();
        }
        if (dis != null) {
          dis.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  //定周期を行うrunメソッドです。
  public void run() {
    do{
      System.gc();//明示的にガベージコレクトを入れています
      DataGet();//各種データゲット
      th.sleep(10000);
    } while (isActive ());
  }


〜蛇足〜
URLConnectionクラスが、WininetAPIを使用していて、これにメモリリークが入り込んでいる・・・ってあるのでしょうか?
http://support.microsoft.com/default.aspx?scid=kb%3Bja%3B814417
http://support.microsoft.com/default.aspx?scid=kb%3Bja%3B812941
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2006-04-22 16:38
Exception は起きていないことは確実なのですよね?(もし catch した場合は e.printStackTrace(); が表示されるはずです、それが表示されないことを確認されているのですよね?)

DataGet を呼びさえしなければ使用メモリは増えないのでしょうか?

disconnect って要らないんでしたっけ?
私はいつもは雰囲気的には、
コード:
URLConnection urlConn = url.openConnection();
InputStream in = urlConn.getInputStream();
// in から読む。
in.close();
HttpURLConnection huc = (HttpURLConnection) urlConn;
huc.disconnect();


のような管理をしています。(エラー処理(catch/finally)は別途必要ですが。)

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
会議室デビュー日: 2004/08/20
投稿数: 16
投稿日時: 2006-04-25 15:31
unibon様、ご回答ありがとうございます。
また、返事が遅くなりまして、申し訳ございません。
引用:

unibonさんの書き込み (2006-04-22 16:38) より:
Exception は起きていないことは確実なのですよね?(もし catch した場合は e.printStackTrace(); が表示されるはずです、それが表示されないことを確認されているのですよね?)

DataGet を呼びさえしなければ使用メモリは増えないのでしょうか?


に関してですが、
Exceptionは起こっておりません。
コンソールを開いて監視してみましたが、e.printStackTrace()は表示されませんでした。
DataGetをコメントアウトすると、ハンドルが増えるようなことはございませんでした。

また、
引用:

disconnect って要らないんでしたっけ?
私はいつもは雰囲気的には、
コード:
URLConnection urlConn = url.openConnection();
InputStream in = urlConn.getInputStream();
// in から読む。
in.close();
HttpURLConnection huc = (HttpURLConnection) urlConn;
huc.disconnect();


のような管理をしています。(エラー処理(catch/finally)は別途必要ですが。)

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}


に関してですが、
DataGet関数を
コード:
  //データを取得する部分です 
  public void DataGet() { 
    InputStream in = null; 
    BufferedReader dis = null; 
    URL url; 
    URLConnection urlConn = null; 
    try { 
      url = new URL(getDocumentBase(), urlcgi);//urlcgiデータ取得ようCGIのURLです。 
      urlConn = url.openConnection(); 
      in = urlConn.getInputStream(); 
      dis = new BufferedReader(new InputStreamReader(in)); 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } finally { 
      try{ 
        if (in != null) { 
          in.close(); 
        } 
        if (dis != null) { 
          dis.close(); 
        } 
        if (urlConn != null) {
          HttpURLConnection huc = (HttpURLConnection) urlConn;
          huc.disconnect();
        }
      } catch (IOException e) { 
        e.printStackTrace(); 
      } 
    } 
  } 


のように変更してみましたが、変化は見られませんでした。
ひょっとしたら、全然別のところで発生しているかもしれませんので、もう少し調査を続けて生きたいと思います。

ありがとうございました。

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