在Windows平台的Tomcat上部署应用后,应用下的个别文件可能会被Tomcat锁住,解部署的时候删除不掉那些内容,就会导致无法重部署。如果解部署删除不掉被锁的文件,Tomcat会在日志中警告说:
严重: [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的话,对所有应用都会生效。
相关推荐
Tomcat 防止跨站请求伪造(CSRF)机制浅析 在 Web 应用开发中,跨站请求伪造(CSRF)是一种常见的安全威胁。跨站请求伪造攻击是指攻击者诱骗受信任用户访问恶意网站,从而使得恶意网站能以用户身份对受信任网站执行...
【深入浅析TomCat Session管理分析】 Session在Java Web开发中扮演着至关重要的角色,它允许服务器跟踪用户的交互,存储用户的状态信息。Tomcat作为广泛使用的开源Servlet容器,其内部的Session管理机制对于理解Web...
【深入浅析Centos 7系统的Tomcat服务器】 Tomcat,这款由Sun的软件架构师詹姆斯·邓肯·戴维森发起的开源项目,如今已成为Apache软件基金会的一部分,是Java Web应用程序的重要载体。Tomcat服务器以其轻量级、高效...
浅析企业如何防止彼得原理.doc
浅析配网自动化通信方式.pdf
GNU-ld链接脚本浅析的知识点涵盖了链接脚本的基本原理、基本概念、格式、命令、内存布局控制以及符号处理等方面。 首先,概论部分介绍了链接脚本的作用。链接脚本是一种控制链接过程的文件,它定义了如何将输入文件...
#资源达人分享计划#
浅析.NET下防止SQL注入式攻击的方法.pdf
近期工作遇到需要业务场景如下,需要每天定时推送给另一系统一批数据,但是由于系统是集群部署的,会造成统一情况下任务争用的情况,所以需要增加分布式锁来保证一定时间范围内有一个Job来完成定时任务. 前期考虑的方案...
浅析生产设备管理控制程序文件.doc
Java技术浅析 Java是一种广泛应用于企业级项目开发的编程语言,它具有强大的功能和灵活的特性,使其成为开发大型项目的首选语言。Java技术浅析主要介绍了Java语言的技术关键词、编程技术框架、原理等知识点。 一、...
小白浅析mwan3的配置文件中文说明,openwrt多拨时使用
有时候用户希望将不同代码放在不同存储空间,也就是通过编译器生成的映像文件需要 包含多个域,每个域在加载和运行时可以有不同的地址。要生成这样的映像文件,必须通过 某种方式告知编译器相关的地址映射关系 2 ...
上传测试
浅析网络安全技术.pdf浅析网络安全技术.pdf浅析网络安全技术.pdf
Oracle与sql简单优化与锁机制浅析
"Java 读写锁实现原理浅析" Java 读写锁实现原理浅析是 Java 并发编程中一个非常重要的主题。在多线程编程中,读写锁是解决读写并发问题的常用机制。本文主要介绍了 Java 读写锁实现原理浅析,包括读写锁的定义、...
浅析 Idea 生成 war 包放入 Tomcat 的路径访问问题 Idea 是一个功能强大且流行的集成开发环境(IDE),它可以帮助开发者快速创建、编译和部署 Java 应用程序。然而,在将 war 包部署到 Tomcat 服务器时,开发者...
②虚拟机文件可复制到主机,但 → 主机文件无法复制到虚拟机。可通过以下方法,从终端直接访问主机文件夹。 1.虚拟机>设置>选项>共享文件夹 点选 “总是启用” “文件夹”>添加>下一步>浏览[主机路径]>下一步 启用...