天天看點

對org.springframework.beans.CachedIntrospectionResults的再次解讀

so the problem here with the cachedintrospectionresults is that it uses beaninfo and propertydescriptor that both have strong reference to the class (indirectly by a reference on methods of the class). that will be solved with jdk 1.5 that uses a combinaison of weak and soft reference to the class and method objects, but for 1.4.2, there's not really any better solution than to flush the introspector's cache and/or use weakreference on cachedintrospectionresults. using weakreference on the cachedintrospectionresults is safer, but decrease performance, and in such case a manual instrospector.flushfromcaches(class) must be used, so that the instrospector does not keep a strong reference on the beaninfo.

when a webapp is hot-redeployed, a new classloader is created to load the webapp, and the old one is thrown away, expected to be garbage collected. for the collection to happen, the server must clear any strong reference to the classloader or its classes, and also the webapp must make sure that any code in parent classloaders (or siblings) clear any reference it might have to any of the webapp's class.

    照他的說法和參考《深入jvm》一書,對class有強引用的有:classloader,java.beans.beaninfo,java.beans.propertydescriptor,java.lang.reflect.method。因為在這個緩存中使用class作為key,而value是cachedintrospectionresults,cachedintrospectionresults中持有beaninfo和method的引用,這兩個都對class對象有強引用(這一點據說在jdk5中已經修改,被改成軟引用和弱引用的組合,而在jdk1.4.2需要這樣的處理),導緻在web應用關閉或者熱部署的時候,舊的classloader和它引用的類不能被回收,是以使用弱引用包裝cachedintrospectionresults對象作為value。web應用關閉或者熱部署的時候,會new新的classloader用于裝載類,這就是cachedintrospectionresults判斷緩存是否safe的根據所在,判斷要緩存的class引用的classloader是否相同。

    當使用javabean的内省時,使用introspector,jdk會自動緩存内省的資訊(beaninfo),這一點可以了解,因為内省通過反射的代價是高昂的。當classloader關閉的時候,introspector的緩存持有beaninfo的資訊,而beaninfo持有class的強引用,這将導緻classloader和它引用的class等對象不能被垃圾收集器回收,是以在關閉前,需要手工清除introspector中的緩存,調用introspector.flushfromcaches,這就是cachedintrospectionresults中當得到beaninfo後為什麼要執行下面這段代碼的原因:

            this.beaninfo = introspector.getbeaninfo(clazz);

            // immediately remove class from introspector cache, to allow for proper

            // garbage collection on class loader shutdown - we cache it here anyway,

            // in a gc-friendly manner. in contrast to cachedintrospectionresults,

            // introspector does not use weakreferences as values of its weakhashmap!

            class classtoflush = clazz;

            do {

                introspector.flushfromcaches(classtoflush);

                classtoflush = classtoflush.getsuperclass();

            }

            while (classtoflush != null);

說到這裡,spring中有一個比較少人注意的listener——org.springframework.web.util.introspectorcleanuplistener,這個類的說明如下:

它是一個在web應用關閉的時候,清除javabeans introspector緩存的監聽器.在web.xml中注冊這個listener.可以保證在web 應用關閉的時候釋放與掉這個web 應用相關的class loader 和由它加載的類

如果你使用了javabeans introspector來分析應用中的類,系統級introspector 緩沖中會保留這些類的hard引用。結果在你的web應用關閉的時候,這些類以及web 應用相關的class loader沒有被垃圾收集器回收.

不幸的是,清除introspector的唯一方式是重新整理整個緩存。這是因為我們沒法判斷哪些是屬于你的應用的引用.是以删除被緩沖的introspection會導緻把這台server上的所有應用的introspection(内省)結果都删掉.

需要注意的是,spring容器托管的bean不需要使用這個監聽器.因為spring它自己的introspection所使用的緩沖在分析完一個類之後會被馬上從javabeans introspector緩沖中清除掉(上面提到的代碼說明了這一點)。

一般的應用基本不會直接用到javabean的内省方法,是以一般不用考慮遇到此類内省資源洩露,但是,很多的類庫或者架構(比如struts,quartz)沒有清除introspector。這個listener就是為它們“擦屁股”的。請注意,這個監聽器需要注冊在web.xml中的所有應用監聽器之前(比如contentloaderlistener之前)。

<listener>

   <listener-class>org.springframework.web.util.introspectorcleanuplistener</listener-class>

</listener>

    參考資料:

 《深入java虛拟機》

文章轉自莊周夢蝶  ,原文釋出時間5.17