Java に関するメモ

何かやろうとしたが、はまってしまい、解決に困ったりしたことなどの記録です。
みなさまの参考になれば幸いです。


HotSpotVM の振る舞いによるメモリリーク

作っていたシステムで、メモリ使用量が時間とともに徐々に増加していくのです。 メモリリークがあるのか?と、JProbe Profiler 使って調べますが、 リークの原因になっているオブジェクトが見つかりません。

JProbe Profiler は アプリケーションが使っているオブジェクトのクラス名や インスタンス数などを表示したり、 特定のインスタンスがどのオブジェクトから参照されているのかをグラフィカルに表示して くれる、とっても便利なツールです。

どのように増加するのかを調べるため、Runtime#totalMemory() と Runtime#freeMemory() を使って、定期的にコンソールにメモリ使用量を出力してみました。 そうすると、確かにコンソールに出力されるメモリ使用量は増加しています。 リーク原因のオブジェクトがないのになぜ増えるのか疑問に思い、 とりあえずコンソール出力の直前に GC を追加してみました。 そしたら、なんとメモリ使用量の増加がなくなってしまったのです。

GC のことはよく知らなかったのですが、 ここに説明があるのを 発見しました。 なんとなく、メモリ使用量が増加する原因を突き止めたように思いました。 インスタンスの寿命が中途半端に長いと、通常の GC では解放されなくなることが あるようです。

このページのちょうど まんなかあたりの図を見てください。

見てくださいじゃなくて、画像を引っ張りました。 もしリンク先がなくなっていたら教えてください。

この図は、JavaVM がインスタンスを管理するための領域を示したものです。 インスタンスが生成されると、図中のもっとも右側にそのインスタンスが登録されます。 時間がたつに連れ、インスタンスの位置が左側にずらされると思ってください。 ほとんどのインスタンスは、生成された直後にその寿命を終えます。 大半のオブジェクトは、メソッドなどのスコープの中で生成され、 メソッドの終了とともにその寿命を終えることを考えれば、 寿命が短いというのがわかると思います。

さて、GC です。GC は、そのインスタンスに対する参照がなくなったかどうかをチェックし、 参照がなければインスタンスを解放します。 参照が残っていれば、JavaVM はインスタンスの位置(寿命)を少し左側に移動します。 ところが、通常の GC はこの図のうちの緑の部分しか参照の有無をチェックしないのです。

ネットワーク機器を監視するアプリケーションを作っていたのですが、 監視するネットワーク機器の数を多くすると(現象が発覚したのは300台以上に設定したとき)、 それにともなって通信のためのインスタンスを大量生産するため、 たくさんのオブジェクトを緑の領域からはみ出させていたようです。

そこで、こちら のページにある HotSpotVM Option を調べてみました。 すると、-XX:NewSize というオプションがそれっぽいじゃないですか。 Default size of new generation なんて。

そもそも、なんで Sparc と Intel でこんなにデフォルト値が違ってるんでしょうかね。

ここの値を大きくしてやると、予想通り、メモリ使用量の増加はぱったりと止まりました。 反対に小さい値を与えると、メモリ使用量がガンガン増加しました。(笑)

というわけで、HotSpotVM は、 場合によってメモリリークに似た現象を引き起こすというお話でした。

Ant の jar タスク

ある日、某MLでこのような質問がありました。

Ant の Jar タスクを使用して、以下のファイル構成Aを、 ファイル構成Bの形で Jar ファイルに格納したい場合はどうすればいいか?

ファイル構成A

+ hoge1
| + A-dir
| | - A1-file
| | - A2-file
| |   :
|
+ hoge2
  + B-dir
    - B1-file
    - B2-file
      :

ファイル構成B
+ A-dir
| - A1-file
| - A2-file
|   :
|
+ B-dir
  - B1-file
  - B2-file
    :

この質問に対し Ant のドキュメントを読んだだけでて動作確認もせずに、

「次のように書けばできませんかねぇ?」

と回答してみたが、既にやったができなかったとの返事。

    <jar basedir="hoge1-dir" update="false" includes="**" />
    <jar basedir="hoge2-dir" update="true" includes="**" />

興味があるので試してみたら、わたくしの環境ではあっさり成功。 実は Ant 1.5.1 には、 Jar タスクの update 属性を true にしても無視される、 という BUG があるようでした。 単に質問した方の環境が Ant 1.5.1 で、 わたくしの環境が Ant 1.5.2 だったというオチでした。

tomcat と JDK のバージョンアップ

tomcat に mobwiki をインストールしました。 このパソコンには、JDK1.3.1 をインストールしていたのですが、 mobwiki の「変更履歴/検索」をクリックすると NoSuchMethod エラーを表示します。 mobster-ML で聞いたら、J2SDK1.4.x で動作確認しているらしい。

さっそく J2SDK1.4.1 をインストールして、tomcat 再起動!

うまくいくかな〜?と試したところ、再び NoSuchMethod エラー。

あれれ?おっかしーなー、とコマンドプロンプトで java -version してみると、 間違いなく 1.4.1 に変わっています。

そうだそうだ、環境変数 JAVA_HOME もだ、と設定して tomcat を再起動し、再挑戦。 またまた NoSuchMethod エラー。

http://localhost:8080/manager/serverinfo を見て確認したら、なぜか 1.3.1 のまま。 変だと思いつつ、パソコンを再起動してみてアクセスしてみますが、 やっぱり NoSuchMethod エラー。

「ま、いっか。tomcat も古い(4.1.18)し、いっちょ最新版にするか!」

と 4.1.24 をインストールしてみましたが、またまた NoSuchMethod エラー。 serverinfo でも 1.3.1 と表示。

ここで何気なく他のページを試してみたら、な、な、なんとアクセスできない。 エラー500って、Apache と tomcat の連携が変になっております。 いろいろとアクセスして試しているうちに、 特定の URL パス以下がアクセス不能になっていることが判明しました。 なんとなんと、tomcat のバージョンアップのために再インストールしたときに server.xml がデフォルトのファイルで上書きされていました。(泣)

追加の情報ですが、server.xml のプロパティで読み取り専用にチェックしておいても 上書きされてしまいますのでご注意ください。

server.xml を CVS から復活させて tomcat 再起動し、とりあえず他のページを復元。

もしや!と思い立ち、regedit.exe を起動して 1.3.1 を検索してみたら、 ありました。1.3.1 の jvm.dll を参照している個所が数箇所。 1.4.1 の jvm.dll を参照するようにレジストリを変更して tomcat 再起動。

やっとこさ、1.4.1 上で tomcat が動作し NoSuchMethod エラーも出なくなりましたとさ。

mobwiki について興味がある人は
こちら

Struts の iterate タグ

自宅で Struts の勉強をしていたときのことです。

JavaWorld 2002年5月号に掲載されていたサンプルプログラムを少しだけ変更した ショッピングカートを作ってみた。動かそうとすると、エラー。

org.apache.jasper.JasperException: No collection found が発生し、動作しません。

ServerSideJava の勉強を始めたばかりで調べ方もよくわからないので、 とりあえず JavaWorld の添付 CD の中身をコピーして試してみたら動きます。

「えー?わけわからん。」

ソースの diff をとってみるが有意な差はない。

サンプルにある struts102indexed.jar は入れてない。

でも、Struts は 1.1 なので indexed タグはサポート済みだ。 (と、JavaWorld の記事の中に書いてある)

賢明な読者の方はお気づきですね。

このエラーの原因がわからず、おおよそ2週間くらい (といっても 1日1〜2時間程度だしやらない日もあるのでトータルは10時間以下ですが) 悩みつづけました。

Exception の発生は indexed タグのあたりにある。 ある日、ソースをながめていてふと思った。

詳しくは JavaWorld 2002年5月号をご覧ください。

Catalog.java は Catalog extends HashMap となっている。 でも HashMap は Collection じゃない。 もしかして「No collection found」ってこれのこと? Catalog extends ArrayList に変更し、関連する JSP なども適当に変更しました。 動かしてみたら、いきなり動きました!

なーんだ、Struts の仕様が変わったのか。(そんなんで納得していいのか?)

注意!  本当に仕様が変わったのか BUG なのかは未確認です。

JSTL で forEach

JSTL を使って Web ページを書いていたんですが、ループをネストした形にしたくなりました。

もともとは実際に書いた例をここに書こうと思っていたのですが、 ここでは単純化した例を紹介します。

メニューを大分類し、その中に詳細メニューを表示する場合です。 コンテンツを追加したら詳細メニューの部分を動的に変更したいこともあるでしょう。 例えば、このページのメニュー部分を考えてみます。

TOP
Profile
Java
  ・メモ
オブジェクト指向
  ・GoFデザインパターン一覧
  ・メモ
XP
      :

まずは大分類をずらっと出力する JSP を書くにはどうすればいいか? なんとなく、<c:forEach> タグを使えばよさそうです。

<%!
public class Menu {
    private String name;
    public Menu(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
}
%>

<%
    Vector v = new Vector();
    Menu menu = new Menu("Profile");
    v.add(menu);
    menu = new Menu("Java");
    v.add(menu);
    menu = new Menu("オブジェクト指向");
    v.add(menu);
    menu = new Menu("XP");
        :

    ServletContext servletContext = pageContext.getServletContext();
    pageContext.setAttribute("menus", v);
%>

<c:forEach items="${menus}" var="m">
    <c:out value="${m.name}" />
    </c:forEach>
</c:forEach>

さて、これにもうひとつ forEach をネストした形にするにはどうしたらいいんでしょう?

できてみれば簡単なことでしたが、これを実現するために何度も試行錯誤しました。

<%!
public class Menu {
    private String name;
    private Vector contents = new Vector();
    public Menu(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public Vector getContents(){
        return contents;
    }
    public void addContent(Content c){
        contents.add(c);
    }
}

public class Content {
    private String name;
    public Content(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
}
%>

<%
    Vector v = new Vector();
    Menu menu = new Menu("Profile");
    v.add(menu);
    menu = new Menu("Java");
    menu.addContent("メモ");
    v.add(menu);
    menu = new Menu("オブジェクト指向");
    menu.addContent("Gofデザインパターン");
    menu.addContent("メモ");
    v.add(menu);
    menu = new Menu("XP");
        :

    ServletContext servletContext = pageContext.getServletContext();
    pageContext.setAttribute("menus", v);
%>

<c:forEach items="${menus}" var="m">
    <c:out value="${m.name}" />
    <ul>
    <c:forEach items="${m.contents}" var="con">
        <li><c:out value="${con.name}" /><r>
    </c:forEach>
    </ul>
</c:forEach>

Menu を forEach の items 属性で取得したオブジェクトにしたとき、 そのメンバが Collection にしておくのがポイントのようです。 そのメンバが Collection ならばネストした forEach が書けることがわかりました。

これってもしかして常識? JSP 経験がないワタクシが知らないだけだったらゴメンなさい。

JSTL のドキュメント

なんのことはない、JSTL のドキュメントを探していました。

でもでも、いくら探しても見つからない。 Sun のサイトと Jakarta のサイトを行ったり来たり。

実は、 JavaServer Pages Technorogy の中の Reference Summary のところの、 JSTL 1.0 というのがドキュメント(リファレンス)でした。

実はそこもそれまでに何度か見ていた(クリックしていた)のですが、 そこのリンクをクリックするといきなり PDF のダウンロードが始まるので、 違うものだと思って無視していました。(^^; Sun のサイトにあるドキュメントは HTML に違いないと言う先入観のために、 まさか PDF がリファレンスだとは思いもしませんでした。

任意の Jarファイルのクラスをインスタンス化する

開発にかかわったパッケージソフトでは、Javaアプリケーションから任意の Jar ファイルを 読み込む、つまりプラグインする機能を持っています。 Eclipse などの IDE でも、後付けで機能をプラグインできたりします。

でも、ちょっと不思議だと思いませんか? JavaVM が読み込み可能なクラスは、CLASSPATH 環境変数に定義されているか、 コマンドラインの classpath 引数で渡されたものなのです。 プラグインのモジュールを動的に追加して、なぜその Jar ファイル内のクラスを使える ようになるのでしょう。

プラグインの仕組みを作るとき、これからここで紹介する方法を見つけるまでは、 かなり悩みました。 よく、ext ディレクトリに入れとけばいい、というような意見を聞きますが、 この方法は適切ではありません。 同じ名前のファイルが既に存在しないとも限りませんし、 ユーザが JRE の新しいバージョンをインストールすると、 いきなり動作しなくなると言った問題を抱えています。

Eclipse のソースは見ていないので、Eclipse がどうやって実現しているのかはわかりません。 でも、私が開発にかかわったパッケージソフトでも同様の(というか、もうちょっと 簡単な仕組みですが)プラグイン機能を実現しています。その秘密は、 java.net.URLClassLoaderにあります。

java.net.URLClassLoaderを使用すると、簡単に任意の Jar ファイル内にあるクラスを 利用可能になります。どのようなコードを書けばいいのでしょうか?

    File file = new File("(Jar ファイルのパス)");
    URL url = file.toURL();
    URLClassLoader loader = new URLClassLoader(url);
    try{
        Class clazz = loader.loadClass("(ロードしたいクラス名)");
	Object target = clazz.newInstance();
    }catch(Exception e){
    }
種明かしするとあっけないですが、わずかこれだけです。 これだけのソースで、任意の場所に置いてある Jarファイル内のクラスをインスタンス化 できるのです。

ついでですが、上記のソース内ではロードしたいクラス名が何なのかわかりません。 一般的には、Jarファイル内の情報は Jarファイルに同梱する マニフェストファイルに記述する約束になっています。 Eclipse のプラグインは独自書式のXMLに記述しているようです。 では、マニフェストファイルにはどのように記述したらいいのでしょうか。 マニフェストファイルの記述方法は、 こちら に詳しく書かれています。 しかし、このようなプラグインという形での使用方法は Sun が想定していないようで、 上記ドキュメントには対応するような属性は定義されていません。 ここで定義されていない属性は、アプリケーション固有の属性として定義します。 その際、定義済みの属性名と同じ名前を付けないように注意しましょう。 安易に、

    Plugin-Class-Name: (ロードしたいクラス名)
のような属性名でもかまわないでしょう。

マニフェストの中身を読むには、以下のクラスを使用します。

整理してソースを書き直してみます。
    File file = new File("(Jar ファイルのパス)");
    JarFile jar = new JarFile(file);
    Manifest manifest = jar.getManifest();
    Attributes attributes = manifest.getAttributes("Plugin-Class-Name");
    String className = attributes.getValue("Plugin-Class-Name");
    URL url = file.toURL();
    URLClassLoader loader = new URLClassLoader(url);
    try{
        Class clazz = loader.loadClass(className);
	Object target = clazz.newInstance();
    }catch(Exception e){
    }
ね、とっても簡単でしょう!





HOME お勧め書籍 / Profile / リンク