浅析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:\tomcat\apache-tomcat-7.0.32\webapps\struts2-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之后,部署应用就可以在work\Catalina\localhost\struts2-blank\loader目录下看到被解压的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的主要代码和主要逻辑为:

    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的话,对所有应用都会生效。

时间: 2024-08-10 22:44:33

浅析Tomcat防止资料被锁的方式的相关文章

在Tomcat中部署Web应用的方式

在Tomcat中部署Web应用的方式有以下几种: 利用Tomcat的自动部署 将一个Web应用复制到Tomcat的webapps下,系统将会把该应用部署到Tomcat中.这是最简单.最常用的方式. 利用控制台部署 启动Tomcat,在命令行执行窗口中,cd D:\Program Files\Java\apache-tomcat-8.0.23\bin,然后startup.bat 浏览器登录http://localhost:8080 进入Manager App控制台(需要在D:\Program Fi

三星s4更改锁屏方式时显示已有管理员、加密政策、或证书存储政策禁用

屏幕锁定设置除密码以外,其他锁屏方式都是灰色且提示"已由管理员.加密政策.或证书存储"字样,此情况是由于手机中设置了设备加密或加密外置SD卡的功能.如需更换锁屏方式,需要先解密设备或解密外置SD卡. 操作: 1.取消设备加密.方式:设定-安全-解密设备-点击"解密设备"-输入锁屏密码-再次点击屏幕下方的"解密设备"字样-等待完成. 注意:取消设备加密时手机需要连接充电器. 三星官网针对解密设备功能制作了图文并茂的功能教程,请参考: http://

浅析C# 异步编程的两种方式

一.传统BeginInvoke方式. BeginInvoke方法用于启动c#异步调用.它返回IasyncResult,可用于监视调用进度.EndInvoke方法用于检索c#异步调用结果. 调用BeginInvoke后可随时调用EndInvoke方法;如果C#异步调用未完成,EndInvoke将一直阻塞到C#异步调用完成. 总结其使用大体分5个步骤: 1.声明委拖 2.创建异步方法 3.实例化委拖(把委拖与方法关联)  A 4.通过实例的BeginInvoke调用异步方法 5.通过实例的EndIn

Mysql InnoDB行锁实现方式(转)

Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁! 在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能.下面通过一些实际例子来加以说明. (1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行

浅析tomcat nio 配置

[尊重原创文章摘自:http://blog.csdn.net/yaerfeng/article/details/7679740] tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志.或者登录他们的默认页面http://localhost:8080/查看其中的服务器状态. 1)bio 默认的模式,性能非常低下,没有经过任何优化处理和支持. 2)nio 利用java的异步io护理技术,no blocking IO技术. 想运行在该模式下,直接

windows + myeclipse 调试 linux + tomcat 的java web服务 配置方式

一.linux tomcat配置和启动 1.catalina.sh第一行添加 declare -x CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000" 2.启动JPDA(这是tomcat下的启动方式,别的服务器可能会不一样,但道理是一样的) ./catalina.sh jpda start 3.启动web服务 ./start.sh 二.在windows端的myecli

Tomcat热部署的三种方式

热部署是指在你修改项目BUG的时候对JSP或JAVA类进行了修改在不重启WEB服务器前提下能让修改生效.但是对配置文件的修改除外! 1.直接把项目web文件夹放在webapps里. 2.在tomcat\conf\server.xml中的<host></host>内部添加<context/>标签: <Context debug="0" docBase="D:\demo1\web" path="/demo1"

使用无锁的方式和有锁的方式的程序性能对比

这里分别使用有锁和无锁两种方式,对一个数值进行增加,一直增加到100000,然后输出使用时间的长短. 1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.atomic.AtomicInteger; 5 6 public class Te

1.tomcat部署项目的几种方式和weblogic部署方式及一点通讯

  第一种部署方式: 直接使用myeclipse 找到server服务 添加要部署的项目Add Deployment ,然后选中某个项目,首选Exploded Archive(development mode) ,然后finish,直接将javaweb项目部署到了 tomcat的webapp目录中,然后启动服务,就将项目挂载到了tomcat服务器中. 第二种部署方式: 找到tomcat所在目录,然后打开tomcat目录下的conf目录,然后找到Catalina目录,打开并找到locahost目录