• 浅谈jrebel


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

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

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

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

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

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

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

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

    什么是双亲委派模型?

    按这个顺序:启动类加载器-》扩展类加载器-》应用程序类加载器-》自定义类加载器。去加载类。也就是当你用自定义类加载器加载某类时,一定要从启动类加载器(JAVA_HOMElib)这个目录中的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()重启上下文就行了。

  • 相关阅读:
    html 带渐变的吸顶效果 vue
    Linux添加环境变量
    C#集合通论
    Android adb 命令导出数据库
    查看签名方式及签名信息
    啥 啥 啥,服务治理是个啥
    令牌桶、漏斗、冷启动限流在sentinel的应用
    MySQL事务
    MySQL优化
    MySQL视图、存储过程、函数、触发器
  • 原文地址:https://www.cnblogs.com/wolf12/p/7173535.html
Copyright © 2020-2023  润新知