元ネタはコチラ。
http://blogs.wankuma.com/kacchan6/archive/2007/07/29/87572.aspx
指摘のメールを投げておいたのですが、回答が来ました。
引用すると、
Java Language Specification 2nd Editionの12.7
(http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html#74294)
を見ると、以下の文章にあるとおり、クラスはシステムクラスローダで生成したクラスはGC
対象から外れていることが記述されています。
Classes and interfaces loaded by the bootstrap loader may not be unloaded.
JDK1.1の頃はクラスもGC対象にしようとしていたようですが、
そのあとのReloading~の部分で説明された論理的な矛盾から、
JDK1.2以降ではシステムクラスローダで生成したクラスはGC対象外となりました。
staticフィールドの領域はクラス単位でヒープ領域に取るようなので、
staticフィールドはAP実行中は存在しているとみなしてよいと判断して、
このような記述をとっています。
※厳密にはstaticイニシャライザの実行タイミングによって変わりますが。
ただし、ユーザクラスローダを利用しているならば、当然この範疇ではありません。
とはいえ、自前でクラスローダを作って何かするケースは
Webの業務AP開発ではまずないと思うので、
特に修正はいらないと思います。
もし、修正するとしても、注釈で「自前のクラスローダを利用している場合は除く」
程度でよいと思います。
「自前でクラスローダを作って何かするケースはWebの業務AP開発ではまずないと思うので、」
というくだりがちょっと気になりました。確かに自前では作らないかもしれませんが、最近のWEB開発ではフレームワークが独自のクラスローダを持っているケースがあります。特にDIコンテナなどユーザが意識しない部分で結構使われていたりします。そもそもWebアプリのクラスローダも、サーブレットコンテナが持つ自前のクラスローダです。
他にもJUnitとかもありますね。連続テストでOutOfMemoryErrorになったりすることもありますが、クラスローダによるメモリリークが意識されていない事が原因だったりします。捨てられるのが前提でロードされているものが、捨てられない領域によって参照されている場合などです。
上記例ではTomcat5.5にはCommons Loggingの古いバージョンが入っていまして、Commons Loggingはシステムクラスローダでロードされています。そしてLogFactoryはクラスローダごとにLogFactoryのインスタンスをLogFactoryのstaticフィールドにキャッシュします。システムクラスローダでロードされたLogFactoryのstaticフィールドにWebAppClassLoaderがキャッシュされることによってメモリリークが発生します。これはリロードの時に発生するので、ServletContextListenerでLogFactory#release(ClassLoader)を呼んであげなければいけません。新しいCommons Loggingは弱参照でクラスローダをキャッシュするので問題ないのですが。
何はともあれ、筆者の意向はよく分かりましたし、正直システムクラスローダで読み込まれたクラスはそもそもGCの対象外というのは勉強になりました。こういう濃いネタで意見すると非常に勉強になりますね。