連載
» 2011年06月24日 00時00分 UPDATE

Tomcat 7の新機能で何ができるようになるのか?(3):技術者が知っておきたいTomcat 7の新機能20連発 (2/3)

[藤野圭一,NTT OSSセンタ]

【3】さまざまな参照をクリアしてメモリリーク防止

 メモリリーク防止機能としてWebappClassLoader#clearReferences()が改良されました。WebappClassLoader#clearReferences()はWebAppクラスローダの停止時に呼び出され、クラスローダが保持する、さまざまな参照をクリアします。

 以下にclearReferences()に新たに追加されたクリア処理をいくつか記載します。

JDBCドライバの参照をクリア

 Webアプリケーションを停止するときに、ロードされているすべてのJDBCドライバから停止するWebアプリケーションのクラスローダによってロードされたJDBCドライバをderegisterDriverします。

 これにより、JDBCドライバの参照残によるWebAppクラスローダのリークを防止します。このとき、以下のようなメッセージがエラーレベルで出力されます。


The web application 【コンテキスト名】 registered the JDBC driver 【ドライバ名】 but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

実行中スレッドの停止

 Webアプリケーション停止時に、停止するWebアプリケーションのWebAppクラスローダがコンテキストクラスローダとなっている全ての実行中スレッドを停止します。

 デフォルトでは、この機能をON/OFFする属性のclearReferencesStopThreadsやclearReferencesStopTimerThreadsがfalseに設定されているため、実際の停止までは行わず、以下のようなエラーメッセージのみを出力します(参考:Apache Tomcat 7 Configuration Reference (7.0.16) - The Context Container)。

リクエスト処理スレッドが実行中の場合

The web application 【コンテキスト名】 is still processing a request that has yet to finish. This is very likely to create a memory leak. You can control the time allowed for requests to finish by using the unloadDelay attribute of the standard Context implementation.

リクエスト処理スレッド以外のスレッドが実行中の場合

The web application【コンテキスト名】 appears to have started a thread named 【スレッド名】 but has failed to stop it. This is very likely to create a memory leak.

削除されていないスレッドローカルのチェック

 Webアプリケーション停止時に、起動中のスレッドに、停止するWebアプリケーションのWebAppクラスローダがロードしたThreadLocalが登録されているかどうかをチェックします。ThreadLocalが残っている場合は、以下のようなエラーログを出力します。


The web application 【コンテキスト名】 created a ThreadLocal with key of type 【キーの型】 (value 【キーのtoString()】) and a value of type 【ThreadLocalの値】 (value 【ThreadLocalのtoString()】) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

 実際のThreadLocalのリークによるWebAppクラスローダのリークを防止は、org.apache.catalina.core.ThreadLocalLeakPreventionListenerによって行われます。ThreadLocalLeakPreventionListenerはTomcat 7.0.6からデフォルトでserver.xmlに設定されています。

 Tomcat 6の場合はThreadLocalLeakPreventionListenerがバックポートされていないので、Context#clearReferencesThreadLocalsでThreadLocalの削除を切り替えます(参考:Apache Tomcat Configuration Reference - The Context Container)。

【4】メモリリークの探知機能「Find leaks」

 ManagerアプリケーションにFind leaksボタンが追加されました。Find leaksでは、Webアプリケーションが停止、リロード、アンデプロイしたときに本来破棄されていなければならないはずのWebAppクラスローダを検索し、もし残っていたらメモリリークと判断して、そのWebアプリケーションのパスを表示します。

 なお、この機能では「System.gc()」メソッド(ガベージ・コレクション)を利用しているため、Javaのオプションなどで無効にしている場合は注意が必要です。

r305.gif

【5】さまざまなTomcatの実行方法が追加

 Tomcatを利用するのに最も一般的な方法は、server.xmlを設定して、起動スクリプトから起動するというものです。Tomcat 7では、上記とは別の方法でTomcatを利用できます。それは、org.apache.catalina.startup.Tomcatクラスを利用する方法とJMXベースでのTomcatのコンフィグする方法です。

org.apache.catalina.startup.Tomcatクラス

 Tomcat 7では、Tomcatをプログラムに組み込むためのAPIを提供します。その肝となるクラスが「o.a.c.startup.Tomcat」クラスです。o.a.c.startup.Tomcatクラスを利用することで数行程度のプログラムでアプリケーションにTomcatを組み込めます。以降では、Tomcatクラスを使ってカスタムTomcatを作成してみましょう。

public class TomcatEmbedded {
public class TomcatEmbedded {
 
    public static void main(String[] args) {
        try {
            Tomcat tomcat = new Tomcat();
            Context ctx = tomcat.addContext("/test", "/test");
            Tomcat.addServlet(ctx, "hello", new HelloServlet());
            ctx.addServletMapping("/helloworld", "hello");
            tomcat.start();
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            // TODO
        }
    }
}
 
// Servlet
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res)
            throws IOException {
        res.getWriter().write("Hello world");
    }
}

 以下で、コードを細かく見ていきます。

Tomcat tomcat = new Tomcat();

 基本となるo.a.c.startup.Tomcatのインスタンスを生成します。

Context ctx = tomcat.addContext("/test", "/test");

 生成したTomcatインスタンスにContextを追加します。第1引数にコンテキストパス、第2引数にコンテキストのdocBaseを指定します。

Tomcat.addServlet(ctx, "hello", new HelloServlet());

 生成したコンテキストにServletを追加します。第1引数にServletを追加するコンテキスト、第2引数に追加するServlet名、第3引数に追加するServletのインスタンスを指定します。

ctx.addServletMapping("/helloworld", "hello");

 コンテキストにServletをマッピングします。第1引数にマッピングするパターン、第2引数にマッピングするServlet名を指定します。

tomcat.start();

 準備ができたので、Tomcatを起動します。

tomcat.getServer().await();

 メインスレッドが終了してしまうので、待ち合わせをします。「tomcat.getServer().await();」を呼び出すことでTomcatインスタンスを起動したメインスレッドがSHUTDOWN待ちの状態になります。

 Webブラウザから「http://localhost:8080/test/helloworld」とリクエストすると”Hello world”と表示されます。数行程度のコードで簡単にTomcatを組み込めました。

JMX経由でTomcatを構成

 Tomcat 7はJMX経由でTomcatを構成する機能を提供します。本機能を利用するには「ObjectName=Catalina:type=MBeanFactory」で定義されているMBeanを使用します。では、MBeanFactoryを使ってTomcatをコンフィグレーションしてみましょう。例えば、以下のような何も定義していないserver.xmlを容易します。

<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
</Server>

 このserver.xmlを指定してTomcatを起動したら準備は完了です。MBeanFactoryを利用してTomcatを作成していきます。今回はJConsoleを利用してみましょう。

r306.gif

 MBeanFactoryには、Tomcatの構成要素を作成し、追加するようなメソッドが多く定義されています。これらのメソッド郡を利用することでTomcatを作成できます。

r307.gif

 まず、createStandardServiceEngineを実行し、Engineを作成します。第3引数のbaseDirにはTomcatをインストールしたディレクトリを指定します。

r308.gif

 次に、createStandardHostを実行し、作成したEngineにHostを登録します。第1引数の「parent」にはcreateStandardServiceEngineの返却値を指定します。第3引数の「appBase」にはアプリケーションを配置するディレクトリを指定します。

r309.gif

 createHttpConnectorを実行し、HTTPリクエストを処理可能にします。後は、Host生成時に指定したappBaseにWebアプリケーションを配置すればTomcatがデプロイしてくれます。

 簡単とは言えないですが、一応MBeanFactoryを利用してTomcatを構成できました。次ページでは、最後にちょっとした変更点を紹介して終わりにしたいと思います。

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。