• 浅析Tomcat防止资料被锁的方式


    浅析Tomcat防止文件被锁的方式(引自:http://www.myexception.cn/open-source/1177067.html)

    在Windows平台的Tomcat上部署应用后,应用下的个别文件可能会被Tomcat锁住,解部署的时候删除不掉那些内容,就会导致无法重部署。如果解部署删除不掉被锁的文件,Tomcat会在日志中警告说:

    2013-1-9 15:44:09 org.apache.catalina.startup.ExpandWar delete
    严重: [D: omcatapache-tomcat-7.0.32webappsstruts2-blank] could not be completely deleted. The presence of the remaining files may cause problems

    被锁的文件通常是/WEB-INF/lib下的Jar包,又以Struts2和XWork的Jar包为甚。

    遇到这个问题,最简单但最折腾的做法就是停止Tomcat、手动删除webapps目录下残留的文件,再重启、重新部署应用。皱眉

    说明:

    测试应用是Struts 2.3.8自带的struts2-blank.war

    源码分析对象是Tomcat 7.0.23

    1. 文件是如何被锁的?

    Tomcat会为每个应用创建一个单独的ClassLoader(WebappClassLoader),负责加载应用使用的Java类和资源。
    WebappClassLoader是java.net.URLClassLoader的子类,URLClassLoader在加载资源的时候会使用getResource方法去访问资源,如果资源文件在Jar包里,那就会打开到Jar包的URL连接,而URLConnection缺省会打开一个缓存、将创建过连接的内容缓存起来,一旦内容被缓存,那相应的Jar包就会被锁定。

    2. 解决方案

    针对Windows上文件被锁、不能重部署应用的问题,Tomcat给出了两个解决方案:

    2.1 从Tomcat 5.0开始,可以在context.xml的Context元素上设置antiJARLocking属性为true;从Tomcat 5.5开始,可以在context.xml的Context元素上设置antiResourceLocking属性为true(说明)

    但在Tomcat 7.0.23(注释掉server.xml里Server元素下的JreMemoryLeakPreventionListener,这个监听器在后面分析)和Tomcat 6.0.20里用struts2-blank.war测试,只有将antiResourceLocking设置为true,Struts和XWork的Jar包才会在解部署时删除。接下来分析一下这两个属性的工作原理。

    (1)antiJARLocking

    先来看看应用的antiJARLocking属性设置为true时,Tomcat是怎么处理的。
    针对antiJARLocking属性的处理集中在WebappClassLoader的getResource和findResourceInternal方法里,主要原理是将包含在Jar包里的资源抽取放到应用的工作目录(work里应用对应的目录)下去。findResourceInternal的主要代码为:

    if (antiJARLocking && !(path.endsWith(".class"))) {
        byte[] buf = new byte[1024];
        File resourceFile = new File
            (loaderDir, jarEntry.getName());
        if (!resourceFile.exists()) {
            Enumeration<JarEntry> entries =
                jarFiles[i].entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry2 =  entries.nextElement();
                if (!(jarEntry2.isDirectory())
                    && (!jarEntry2.getName().endsWith
                        (".class"))) {
                    resourceFile = new File
                        (loaderDir, jarEntry2.getName());
                    ......
                    File parentFile = resourceFile.getParentFile();
                    if (!parentFile.mkdirs() && !parentFile.exists()) {
                        // Ignore the error (like the IOExceptions below)
                    }
                    FileOutputStream os = null;
                    InputStream is = null;
                    try {
                        is = jarFiles[i].getInputStream
                            (jarEntry2);
                        os = new FileOutputStream
                            (resourceFile);
                        while (true) {
                            int n = is.read(buf);
                            if (n <= 0) {
                                break;
                            }
                            os.write(buf, 0, n);
                        }
                        resourceFile.setLastModified(
                                jarEntry2.getTime());
                    } catch (IOException e) {
                        // Ignore
                    } finally {
                        // 关闭流、释放资源
                    }
                }
            }
        }
    }

    把这个属性设置为true之后,部署应用就可以在workCatalinalocalhoststruts2-blankloader目录下看到被解压的Jar包内容。

    antiJARLocking属性在有的时候并不会生效,从WebappClassLoader的getResource和findResource方法逻辑里可以看出一些端倪,在一些情况下(通过对Loader的delegate、searchExternalFirst等相关属性进行配置),资源的获取并不是WebappClassLoader去做的,而是其父加载器的getResource方法或父类的findResource方法去做的,WebappClassLoader的父类是URLClassLoader、父加载器是URLClassLoader实例。

    (2)antiResourceLocking

    当antiResourceLocking设置为true的时候,Tomcat不会锁定应用下的任何文件。那Tomcat是怎么做到这一点的呢?
    在Tomcat的架构里,应用也是一个级别的容器,对应的接口是Context;各级容器本身都具备生命周期,而且配置了多个生命周期监听器来监听容器不同的生命周期过程。Tomcat在初始化的时候,给Context增加了一个生命周期监听器org.apache.catalina.startup.ContextConfig;然后在Context真正开始启动之前,会有一个BEFORE_START_EVENT状态,ContextConfig监听到这个状态的事件后,就会针对antiResourceLocking进行处理。

    antiLocking
    antiLocking的主要代码和主要逻辑为:

        protected void antiLocking() {
            // 只针对应用做处理,应用的antiResourceLocking属性设置为true
            if ((context instanceof StandardContext)
                && ((StandardContext) context).getAntiResourceLocking()) {
               
                // 获取应用原始的doc base(docBaseFile变量,缺省是webapps下以应用名称为名的目录)
                Host host = (Host) context.getParent();
                String appBase = host.getAppBase();
                String docBase = context.getDocBase();
                if (docBase == null)
                    return;
                if (originalDocBase == null) {
                    originalDocBase = docBase;
                } else {
                    docBase = originalDocBase;
                }
                File docBaseFile = new File(docBase);
                if (!docBaseFile.isAbsolute()) {
                    File file = new File(appBase);
                    if (!file.isAbsolute()) {
                        file = new File(getBaseDir(), appBase);
                    }
                    docBaseFile = new File(file, docBase);
                }
               
                // 获取应用的“path + version”,作为新的doc base,version通常是空
                String path = context.getPath();
                if (path == null) {
                    return;
                }
                ContextName cn = new ContextName(path, context.getWebappVersion());
                docBase = cn.getBaseName();
    
                // 在java.io.tmpdir下新建目录
                File file = null;
                if (originalDocBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
                    file = new File(System.getProperty("java.io.tmpdir"),
                            deploymentCount++ + "-" + docBase + ".war");
                } else {
                    file = new File(System.getProperty("java.io.tmpdir"),
                            deploymentCount++ + "-" + docBase);
                }
    
                // 将应用的内容从原始doc base拷贝到java.io.tmpdir下新建的目录中,
                // 并将临时目录下的内容作为应用的doc base
                ExpandWar.delete(file);
                if (ExpandWar.copy(docBaseFile, file)) {
                    context.setDocBase(file.getAbsolutePath());
                }           
            }       
        }

    总结一下,就是如果应用的antiResourceLocking属性设置为true,就将应用的doc base移到临时目录下,让Tomca不会占用webapps下的文件。Tomcat里java.io.tmpdir默认指向Tomcat的temp目录。

    副作用
    从上面的分析来看,antiResourceLocking为true有几个副作用:
    1) 会延长应用的启动时间,因为多了临时目录的清理和往临时目录拷贝应用内容的操作;
    2) 如果不知道这个属性的原理,修改webapps下应用的JSP,那就不会动态重加载到新的页面内容了,因为应用的doc base已经不再在webapps下了;
    3) 停止Tomcat的时候,临时目录下实际的doc base会被删掉,代码如下:

        protected synchronized void configureStop() {
            ......
            Host host = (Host) context.getParent();
            String appBase = host.getAppBase();
            String docBase = context.getDocBase();
            // originalDocBase变量初始为null,只有antiResourceLocking为true时才会赋值
            if ((docBase != null) && (originalDocBase != null)) {
                File docBaseFile = new File(docBase);
                if (!docBaseFile.isAbsolute()) {
                    docBaseFile = new File(appBase, docBase);
                }
                // 删除
                ExpandWar.delete(docBaseFile, false);
            }
            ......
        }

    结合第二条和第三条,如果要修改应用的JSP,那必须将改动同时拷贝到两个目录下(原始doc base和临时目录下的doc base)。
    所以Tomcat里这个属性缺省为false。在使用Tomcat 6.0.24之前的版本时,如果要用这个属性解决文件被锁的问题,三思而行。


    2.2 从Tomcat 6.0.24开始,可以在server.xml的Server元素下配置JreMemoryLeakPreventionListener的urlCacheProtection属性为true(说明)


    这个监听器有诸多属性,其中解决文件被锁的是urlCacheProtection属性。urlCacheProtection的原理很简单,就是针对文件被锁的根本原因进行处理——在Server(Tomcat的顶级容器)初始化之前就将URLConnection的缓存关掉。

    // Set the default URL caching policy to not to cache
    if (urlCacheProtection) {
        try {
            // Doesn't matter that this JAR doesn't exist - just as
            // long as the URL is well-formed
            URL url = new URL("jar:file://dummy.jar!/");
            URLConnection uConn = url.openConnection();
            uConn.setDefaultUseCaches(false); // 修改URLConnection私有的静态变量defaultUserCaches
        } catch (MalformedURLException e) {
            log.error(sm.getString("jreLeakListener.jarUrlConnCacheFail"), e);
        } catch (IOException e) {
            log.error(sm.getString("jreLeakListener.jarUrlConnCacheFail"), e);
        }
    }

    这个监听器是缺省配置的,urlCacheProtection属性也缺省为true,所以从Tomcat 6.0.24开始,文件被锁定的问题就不存在了。

    另外需要注意的是,JreMemoryLeakPreventionListener这个监听器只能设置给Server(Tomcat的顶级容器),所以urlCacheProtection设置为true的话,对所有应用都会生效。

  • 相关阅读:
    由浅入深——从ArrayList浅谈并发容器
    Lambda表达式
    JVM初体验
    Redis主从复制
    Redis事务
    Redis基本命令
    Redis概述与安装
    Linux之SSH免密登录
    源码安装Nginx以及用systemctl管理
    CentOS 7 :Failed to start IPv4 firewall with iptables.
  • 原文地址:https://www.cnblogs.com/honganan/p/3987910.html
Copyright © 2020-2023  润新知