首页 > 代码库 > 浅谈jrebel

浅谈jrebel

有个同事提高个jrebel的工具,提起tomcat的热部署方案。

 

jrebel是一款收费的JVM级的热部署工具包。

 

JVM级的热部署也就是说,可以不重启JVM,让修改或添加的类加载到JVM中。

 

加载器:启动类加载器-》扩展类加载器-》应用程序类加载器-》自定义类加载器。

 

JVM来说只有一种启动类加载器(Bootstrap ClassLoader)是C++写的。

这个类加载器负责将存放在<JAVA_HOME\lib目录中的,

或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用

 

Java实现的加载器的:扩展类加载器,应用程序类加载器,自定义类加载器。

 

什么是双亲委派模型?

 

按这个顺序:启动类加载器-》扩展类加载器-》应用程序类加载器-》自定义类加载器。去加载类。也就是当你用自定义类加载器加载某类时,一定要从启动类加载器(JAVA_HOME\lib)这个目录中的jar中找先。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。

 

 

打破双亲委派模型

如果基础类又要调用回用户的代码,那该怎么办?这并非是不可能的事情,一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK 1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的Class Path下的JNDI接口提供者(SPIService Provider Interface)的代码,但启动类加载器不可能“认识”这些代码,因为启动类加载器的搜索范围中找不到用户应用程序类,那该怎么办?为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置

 

 

Tomcat的类加载器架构

 

Tomcat目录结构中,有3组目录(“/common/*”、“/server/*”和“/shared/*”)可以存放Java类库,另外还可以加上Web应用程序自身的目录“/WEB-INF/*”,一共4组,把Java类库放置在这些目录中的含义分别如下:

   ①放置在/common目录中:类库可被Tomcat和所有的Web应用程序共同使用。

   ②放置在/server目录中:类库可被Tomcat使用,对所有的Web应用程序都不可见。

   ③放置在/shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。

   ④放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。

 

CommonClassLoaderCatalinaClassLoaderSharedClassLoaderWebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/*/server/*/shared/*/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。Tomcat 6.x顺理成章地把/common/server/shared三个目录默认合并到一起变成一个/lib目录,这个目录里的类库相当于以前/common目录中类库的作用。

 

Tomcatserver.xml中的 reloadble=true

<Context docBase="" path="" reloadable="true" source=""/></Host>

Tomcat的加载是怎样的呢?看看tomcaat源码。

 

   public void backgroundProcess() {

        if (reloadable && modified()) {

            try {

                Thread.currentThread().setContextClassLoader

                    (WebappLoader.class.getClassLoader());

                if (context != null) {

                    context.reload();

                }

            } finally {

                if (context != null && context.getLoader() != null) {

                    Thread.currentThread().setContextClassLoader

                        (context.getLoader().getClassLoader());

                }

            }

        }

}

 

有个后台线程扫.class文件,如果有修改就使用(Appclassloader应用程序类加载器去动态加载被修改过的classJVM中:context.reload()重启上下文。也是就是重启当前的WEB应用程序;。而不是重启jvm去静态加载.class

 

请看这个if:if (reloadable && modified()) ,如果reloadble=true而且有.class文件被修改过。就会重启上下文件。那么添加.class会不会也重启上下文呢?请看下面代码:

 

   public boolean modified() {

 

        if (log.isDebugEnabled())

            log.debug("modified()");

 

        for (Entry<String,ResourceEntry> entry : resourceEntries.entrySet()) {

            long cachedLastModified = entry.getValue().lastModified;

            long lastModified = resources.getClassLoaderResource(

                    entry.getKey()).getLastModified();

            if (lastModified != cachedLastModified) {

                if( log.isDebugEnabled() )

                    log.debug(sm.getString("webappClassLoader.resourceModified",

                            entry.getKey(),

                            new Date(cachedLastModified),

                            new Date(lastModified)));

                return true;

            }

        }

 

        // Check if JARs have been added or removed

        WebResource[] jars = resources.listResources("/WEB-INF/lib");

        // Filter out non-JAR resources

 

        int jarCount = 0;

        for (WebResource jar : jars) {

            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {

                jarCount++;

                Long recordedLastModified = jarModificationTimes.get(jar.getName());

                if (recordedLastModified == null) {

                    // Jar has been added

                    log.info(sm.getString("webappClassLoader.jarsAdded",

                            resources.getContext().getName()));

                    return true;

                }

                if (recordedLastModified.longValue() != jar.getLastModified()) {

                    // Jar has been changed

                    log.info(sm.getString("webappClassLoader.jarsModified",

                            resources.getContext().getName()));

                    return true;

                }

            }

        }

 

        if (jarCount < jarModificationTimes.size()){

            log.info(sm.getString("webappClassLoader.jarsRemoved",

                    resources.getContext().getName()));

            return true;

        }

 

 

        // No classes have been modified

        return false;

    }

 

 

 

看到//jar has been added没有,所以肯定也会重启加载。

 

 

Jrebel热部署的原理就很清晰了,就是只加载调用appclassloader加载.classjvm中,而不context.reload()重启上下文就行了。

浅谈jrebel