对org.springframework.beans.CachedIntrospectionResults的再次解读

简介:
这个类在spring2.01前没有被改写,spring2.06似乎已经改写了,还未看源码。不过这不是我所在意的问题。我在《 org.springframework.beans简单解读》中的对这个类的理解是不正确的。我们先看看Guillaume Poirier对这个类中为什么使用WeakHashMap的解释:

WeakHashMap is implemented with WeakReference for keys, and strong reference for values. That means if the value has a strong reference on the key, then the key cannot be garbage collected until the WeakHashMap is ready for collection. However, if the value has no strong reference on its key, then being in the WeakHashMap won't prevent the key and value from being garbage collected if it is otherwise ready. The WeakHashMap knows when to remove the key (and the value with it) by using the notification provided by the java.lang.ref package. For more information on this, see: http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ref/package-summary.html

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虚拟机》
 《Class对象什么时候被回收?
 《Spring API Doc
  《ss目前的设计有引起内存泄露而导致down机的隐患
 以及一篇非常好的解释java引用类的文章《Java对象的强、软、弱和虚引用

文章转自庄周梦蝶  ,原文发布时间5.17

目录
相关文章
|
3月前
Consider defining a bean of type 'org.springframework.security.authentication.AuthenticationManager' in your configuration.
Consider defining a bean of type 'org.springframework.security.authentication.AuthenticationManager' in your configuration.
100 0
|
4月前
|
Java Apache Spring
若依配Mapper,若依修改Caused by: org.springframework.beans.factory.BeanCreationException: Error creating
若依配Mapper,若依修改Caused by: org.springframework.beans.factory.BeanCreationException: Error creating
|
6月前
|
Java
SpringBoot注入出现@org.springframework.beans.factory.annotation.Autowired(required=true)
SpringBoot注入出现@org.springframework.beans.factory.annotation.Autowired(required=true)
89 0
|
Java 微服务 Spring
【Java异常】Spring boot启动失败@org.springframework.beans.factory.annotation.Autowired(required=true)
【Java异常】Spring boot启动失败@org.springframework.beans.factory.annotation.Autowired(required=true)
247 0
|
6月前
解决The injection point has the following annotations:@org.springframework.beans.factory.annotation错误~
解决The injection point has the following annotations:@org.springframework.beans.factory.annotation错误~
1068 0
解决:org.springframework.web.method.annotation.MethodArgumentTypeMismatchExceptio
解决:org.springframework.web.method.annotation.MethodArgumentTypeMismatchExceptio
468 0
No qualifying bean of type ‘org.springframework.boot.autoconfigure.http.HttpMessageConverters‘ avail
No qualifying bean of type ‘org.springframework.boot.autoconfigure.http.HttpMessageConverters‘ avail
158 1
|
JavaScript 前端开发
org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression...
org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression...
144 0
|
Java 数据库连接 Spring
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘ XXX‘
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘ XXX‘
277 0