猪儿笨笨的文档

主要是个人的一些思考和技术文章,还有许多翻译的文档

 

Wicket1.3中Class热加载--揭秘篇

 

在文章《Wicket1.3中Class热加载--使用篇》中,展示了如何使用Wicket1.3提供的ReloadingWicketFilter来动态加载修改后的类(包括修改了签名的类),从而实现高效开发。但在该文章中,只是给出了如何使用该功能的说明,而本篇文章将明确解析Wicket1.3类热加载魔法的奥秘所在。

在上一篇文章中,是通过修改Wicket项目中web.xml文件,将其中的

org.apache.wicket.protocol.http.WicketFilter

全部替换成

org.apache.wicket.protocol.http.ReloadingWicketFilter

从而开启了Wicket类热加载的功能。那么为了探究其魔法奥秘,入手点自然就选择ReloadingWicketFilter这个类了。

先来看一下ReloadingWicketFilter的代码,惊人的少:

public class ReloadingWicketFilter extends WicketFilter

{

private ReloadingClassLoader reloadingClassLoader;

/**

 * Instantiate the reloading class loader

 */

public ReloadingWicketFilter()

{

// Create a reloading classloader

reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader());

}

/**

 * @see org.apache.wicket.protocol.http.WicketFilter#getClassLoader()

 */

protected ClassLoader getClassLoader()

{

return reloadingClassLoader;

}

/**

 * @see org.apache.wicket.protocol.http.WicketFilter#init(javax.servlet.FilterConfig)

 */

public void init(final FilterConfig filterConfig) throws ServletException

{

reloadingClassLoader.setListener(new IChangeListener()

{

public void onChange()

{

// Remove the ModificationWatcher from the current reloading class loader

reloadingClassLoader.destroy();

/*

 * Create a new classloader, as there is no way to clear a ClassLoader's cache. This

 * supposes that we don't share objects across application instances, this is almost

 * true, except for Wicket's Session object.

 */

reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader());

try

{

init(filterConfig);

}

catch (ServletException e)

{

Throw new RuntimeException(e);

}

}

});

super.init(filterConfig);

}

}

其中最引人注目的就是那个ReloadingClassLoader,再打开WicketFilter类中,很容易找到以下代码,它表示使用自定义的ClassLoader来加载类。

final ClassLoader newClassLoader = getClassLoader();

Thread.currentThread().setContextClassLoader(newClassLoader);

而ReloadingWicketFilter则是重载了getClassLoader方法,以返回了自定义的ReloadingClassLoader。也就是说Wicket的魔法其实是使用了一个自定义的ReloadingClassLoader来实现类的热加载(包括对签名被修改的类)。为了让大家更清楚理解Wicket这一方法以,在分析ReloadingClassLoader之前,先来简单的过一下JVM的类加载机制。

在JDK1.2以后,JVM在加载类时默认采用的是双亲委机制(早期的类加载机制存在安全漏洞)。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,所有 ClassLoaders 的根是系统 ClassLoader,它会以缺省方式装入类本地文件系统加载(可能是Jar包,也可能是Class文件),如果父类加载器可以完成类加载任务,就成功返回加载后的类但如果父类加载器无法完成此加载任务时,那么这个特定的类加载才自己去加载指定名称的类这样的双亲委机制可以保证象java.io.*这种基础类库的内容一定是被系统ClassLoader加载,从而保证类加载的安全性。

下面是双亲委派机制的示意图:

实例分析Web应用下的类加载顺序:

为了更好的方便大家理解类的加载机制,并说明Wicket如何使用自定义的ClassLoader来加载更改后的类,下面将有一个简单的实例来说明。

首先将前一篇文章中的HelloWorld代码修改为:

public class HelloWorld extends WicketExamplePage

{

/**

 * Constructor

 */

public HelloWorld()

{

ClassLoader classLoader = this.getClass().getClassLoader();

while (null != classLoader) 

{

System.err.println("loader   " + classLoader.hashCode()+"   "+classLoader.getClass());

classLoader = classLoader.getParent();

}

add(new Label("message""New Hello World!"));

}

}

修改以后的代码,可以在对象被创建时,输出HelloWorld类的ClassLoader及其父ClassLoader,接下来恢复web.xml文件中的filter为WicketFilter,不使用RelodingWicketFilter,从而观察原先的Class加载顺序,得到的结果为:

loader   2011334   class org.apache.catalina.loader.WebappClassLoader

loader   19608393   class org.apache.catalina.loader.StandardClassLoader

loader   13756574   class org.apache.catalina.loader.StandardClassLoader

loader   26726999   class sun.misc.Launcher$AppClassLoader

loader   7494106   class sun.misc.Launcher$ExtClassLoader

接下来仍然按照上一篇文章中的操作修改web.xml,使用RelodingWicketFilter,开启Wicket类的热加载功能。再看一下输出结果:

loader   8310913   class org.apache.wicket.application.ReloadingClassLoader

loader   2011334   class org.apache.catalina.loader.WebappClassLoader

loader   19608393   class org.apache.catalina.loader.StandardClassLoader

loader   13756574   class org.apache.catalina.loader.StandardClassLoader

loader   26726999   class sun.misc.Launcher$AppClassLoader

loader   7494106   class sun.misc.Launcher$ExtClassLoader

可见通过代码

final ClassLoader newClassLoader = getClassLoader();

Thread.currentThread().setContextClassLoader(newClassLoader);

ReloadingWicketFilter使用ReloadingClassLoader作为当前类的ClassLoader,也就是说它接管了所有WEB-INF/classes目录下面类文件的加载。这样它就可以根据实际情况来加载一个类。但有经验的程序员都有知道,一般来说Class一旦被加载,就表示在整个JVM生命周期的过程中,不会自动释放,而是放置在内存中。那么Wicket又是怎么释放已经加载的类,同时加载修改后的类呢?看一段ReloadingWicketFilter中init方法的代码:

/**

 * @see org.apache.wicket.protocol.http.WicketFilter#init(javax.servlet.FilterConfig)

 */

public void init(final FilterConfig filterConfig) throws ServletException

{

reloadingClassLoader.setListener(new IChangeListener()

{

public void onChange()

{

// Remove the ModificationWatcher from the current reloading class loader

reloadingClassLoader.destroy();

/*

 * Create a new classloader, as there is no way to clear a ClassLoader's cache. This

 * supposes that we don't share objects across application instances, this is almost

 * true, except for Wicket's Session object.

 */

reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader());

try

{

init(filterConfig);

}

catch (ServletException e)

{

Throw new RuntimeException(e);

}

}

});

super.init(filterConfig);

}

这段代码表示,一旦发现类文件的改变,将会销毁当前的reloadingClassLoader ,同时新建一个ReloadingClassLoader的实例,因为ClassLoader被销毁了,所以由该ClassLoader加载的类都将被销毁,然后再由新的ReloadingClassLoader进行加载,因此即使是修改了签名的类也是可以被正确加载成功的。当然这种做法,可能会引起Session中的数据不能正确识别和转换。但相对于开发环境下,开发人员通过另起一个新的Session就可以开始正常的工作了,还是可以有效的提高开发效率。

Wicket类加载的一个潜在问题:

如果仅仅使用Wicket开发程序,那么Wicket1.3引入的热加载机制,对开发人员来说,会是一件非常幸福的事情,但如果同时使用了jsp,也就是说同时在一个Web应用程序(不是Web应用服务器)中同时使用Wicket和JSP,而且在Wicket和JSP代码分别向Session中写入相同的对象,如用户信息之类的数据对象,那么就会出现一些不必要的问题,最觉的莫过于ClassCastException。虽然共用Wicket+JSP的情况比较少,出问题的机率也比较少,但还是要额外提出来作为一个警示。

下面是一个简单的类Person代码,用来展示如何出现ClassCastException:

public class Person 

{

/**

 * Default constructor

 */

public Person() 

{

super();

ClassLoader classLoader = this.getClass().getClassLoader();

while (null != classLoader) 

{

System.err.println("loader   " + classLoader.hashCode()+"   "+classLoader.getClass());

classLoader = classLoader.getParent();

}

}

}

在构造函数中的那段代码,可以输出它的ClassLoader顺序,接下来象先前一样访问那个HelloWorld应用,得到如下的ClassLoader顺序:

loader   8310913   class org.apache.wicket.application.ReloadingClassLoader

loader   3862294   class org.apache.catalina.loader.WebappClassLoader

loader   19608393   class org.apache.catalina.loader.StandardClassLoader

loader   13756574   class org.apache.catalina.loader.StandardClassLoader

loader   26726999   class sun.misc.Launcher$AppClassLoader

loader   7494106   class sun.misc.Launcher$ExtClassLoader

然后再写一个run.jsp,代码如下:

<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@page import="org.apache.wicket.examples.Person"%>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Class Loader Demo</title>

</head>

<body>

<%

new Person();

%>

</body>

</html>

在run.jsp中,初始化一个Person对象,同样观察它的输出结果,得到另外一个不同的Class加载顺序:

loader   3862294   class org.apache.catalina.loader.WebappClassLoader

loader   19608393   class org.apache.catalina.loader.StandardClassLoader

loader   13756574   class org.apache.catalina.loader.StandardClassLoader

loader   26726999   class sun.misc.Launcher$AppClassLoader

loader   7494106   class sun.misc.Launcher$ExtClassLoader

可见在JSP中的Person与在HelloWorld中的Person是由不同的ClassLoader加载的(JSP编译成Servlet执行,在Tomcat中,org.apache.jasper.servlet.JasperLoader负责JSP编译后的类加载),根据JVM规范,这两个Class是不等价的。因此进行转换的时候,会引起ClassCastException,这是特别需要注意的一点。


点击这里下载Word格式

posted on 2008-11-24 11:44 猪儿笨笨 阅读(1707) 评论(3)  编辑  收藏 所属分类: Java开发组件设计开源软件Wicket

评论

# re: Wicket1.3中Class热加载--揭秘篇[未登录] 2008-11-25 10:18 beansoft

Create a new classloader, as there is no way to clear a ClassLoader's cache.

类加载器里面的类信息是无法被清理掉的, 这也是为什么reload几次应用就会出现OOM的原因, 个人认为是每个类的.class对象都无法被垃圾回收, 例如Spring+Hibernate会动态生成N多类, 很容易导致perm溢出问题.
最近在看JavaRebel, 对免费类热加载很感兴趣, 因此打开了一下 ReloadingClassLoader 的源码, 其destory()方法的实现的确没有办法清除类信息:
/**
* Remove the ModificationWatcher from the current reloading class loader
*/
public void destroy()
{
watcher.destroy();
}  回复  更多评论   

# re: Wicket1.3中Class热加载--揭秘篇[未登录] 2008-11-25 10:18 beansoft

感谢提供好信息!! 不错, 您研究的够深入!  回复  更多评论   

# re: Wicket1.3中Class热加载--揭秘篇[未登录] 2008-11-25 12:14 猪儿笨笨

是否可以被清除,这个其实在JVM中应该是没有规定的
当然Real JVM可能有所规定
但我估计对于自动GC机制,很难准确分析哪些类没有被占用,或者即使分析成本也比较高,所以常见的JVM是没有处理的。
虽然没有能够释放,但是对于开放人员来讲,这个功能还是非常实用的。
所以我在上篇中也说,这个功能在开发时用用OK,千万不要在上线系统中用。  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

常用链接

留言簿(18)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜