JAR文件句柄:烦恼后清理!
在Ultra ESB中,我们使用特殊的热交换类加载器 ,该加载器使我们可以按需重新加载Java类。 这使我们能够从字面上热交换我们的部署单元 -加载,卸载,使用更新的类重新加载,以及正常地逐步退出-无需重启JVM。
Windows:支持禁地
在Ultra ESB Legacy中 ,加载程序在Windows上可以正常运行,但在较新的X版本上 ,似乎有些困难。 我们不支持将Windows作为目标平台,因此并没有太大的意义-直到最近,当我们决定在Windows上支持非生产发行版时。 (我们的企业集成IDE UltraStudio在Windows上可以很好地运行,因此Windows开发人员都可以使用。)
TDD FTW
修复类加载器很容易,所有测试都通过了; 但是我想通过一些额外的测试来支持我的修正,所以我写了一些新的测试。 其中大多数涉及在系统temp目录下的子目录中创建一个新的JAR文件,并使用热交换类加载器加载放置在JAR中的不同工件。 为了获得更多有关最佳做法的荣誉,我还确保添加一些清除逻辑,以通过FileUtils.deleteDirectory()
删除temp子目录。
然后,事情发疯了 。
拆解不再了。
在Linux和Windows上,所有测试都通过了; 但是最终的拆卸逻辑在Windows中失败了,就在我删除temp子目录的那一刻。
在Windows上,我没有lsof
的奢华; 幸运的是, Sysinternals已经有了我需要的东西: handle64
。
查找罪魁祸首非常容易:在删除目录树之前,在tearDown()
一个断点,然后运行handle64 {my-jar-name}.jar
。
笨蛋
我的测试Java进程持有测试JAR文件的句柄。
寻找泄漏
不行 我没有
自然,我的第一个怀疑者是类加载器本身。 我花了将近半小时反复遍历类加载器代码库。 没运气。 一切似乎都坚如磐石。
又名我的死神文件句柄
我最好的镜头是看是什么代码打开了JAR文件的处理程序。 因此,我为Java的FileInputStream
和FilterInputStream
编写了一个快速处理的补丁程序 ,该补丁程序将转储获取时间的堆栈跟踪快照。 每当线程保持流打开时间过长时。
此“泄漏转储程序”部分受我们的JDBC连接池的启发,该连接池检测到未释放的连接(受宽限期限制),然后转储借用它的线程的堆栈跟踪-回到被借用的时间。 (荣誉给Sachini ,我以前的同事,实习生AdroitLogic 。)
泄漏,裸露!
果然,堆栈跟踪揭示了罪魁祸首:
id: 174 created: 1570560438355 --filter-- java.io.FilterInputStream.<init>(FilterInputStream.java: 13 ) java.util.zip.InflaterInputStream.<init>(InflaterInputStream.java: 81 ) java.util.zip.ZipFile$ZipFileInflaterInputStream.<init>(ZipFile.java: 408 ) java.util.zip.ZipFile.getInputStream(ZipFile.java: 389 ) java.util.jar.JarFile.getInputStream(JarFile.java: 447 ) sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java: 162 ) java.net.URL.openStream(URL.java: 1045 ) org.adroitlogic.x.base.util.HotSwapClassLoader.loadSwappableClass(HotSwapClassLoader.java: 175 ) org.adroitlogic.x.base.util.HotSwapClassLoader.loadClass(HotSwapClassLoader.java: 110 ) org.adroitlogic.x.base.util.HotSwapClassLoaderTest.testServiceLoader(HotSwapClassLoaderTest.java: 128 ) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 ) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 ) java.lang.reflect.Method.invoke(Method.java: 498 ) org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java: 86 ) org.testng.internal.Invoker.invokeMethod(Invoker.java: 643 ) org.testng.internal.Invoker.invokeTestMethod(Invoker.java: 820 ) org.testng.internal.Invoker.invokeTestMethods(Invoker.java: 1128 ) org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java: 129 ) org.testng.internal.TestMethodWorker.run(TestMethodWorker.java: 112 ) org.testng.TestRunner.privateRun(TestRunner.java: 782 ) org.testng.TestRunner.run(TestRunner.java: 632 ) org.testng.SuiteRunner.runTest(SuiteRunner.java: 366 ) org.testng.SuiteRunner.runSequentially(SuiteRunner.java: 361 ) org.testng.SuiteRunner.privateRun(SuiteRunner.java: 319 ) org.testng.SuiteRunner.run(SuiteRunner.java: 268 ) org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java: 52 ) org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java: 86 ) org.testng.TestNG.runSuitesSequentially(TestNG.java: 1244 ) org.testng.TestNG.runSuitesLocally(TestNG.java: 1169 ) org.testng.TestNG.run(TestNG.java: 1064 ) org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java: 72 ) org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java: 123 )
知道了!
java.io.FilterInputStream.<init>(FilterInputStream.java: 13 ) ... sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java: 162 ) java.net.URL.openStream(URL.java: 1045 ) org.adroitlogic.x.base.util.HotSwapClassLoader.loadSwappableClass(HotSwapClassLoader.java: 175 )
但是,这并不能说明全部情况。 如果URL.openStream()
打开JAR,为什么我们从try-with-resources块返回时却没有关闭它?
try (InputStream is = jarURI.toURL().openStream()) { byte [] bytes = IOUtils.toByteArray(is); Class<?> clazz = defineClass(className, bytes, 0 , bytes.length); ... logger.trace( 15 , "Loaded class {} as a swappable class" , className); return clazz; } catch (IOException e) { logger.warn( 16 , "Class {} located as a swappable class, but couldn't be loaded due to : {}, " + "trying to load the class as a usual class" , className, e.getMessage()); ... }
疯狂:
感谢Sun Microsystems使其成为OSS ,我可以浏览JDK源代码,直到这个令人震惊的评论–一直到java.net.URLConnection
:
private static boolean defaultUseCaches = true ; /** * If <code>true</code>, the protocol is allowed to use caching * whenever it can. If <code>false</code>, the protocol must always * try to get a fresh copy of the object. * <p> * This field is set by the <code>setUseCaches</code> method. Its * value is returned by the <code>getUseCaches</code> method. * <p> * Its default value is the value given in the last invocation of the * <code>setDefaultUseCaches</code> method. * * @see java.net.URLConnection#setUseCaches(boolean) * @see java.net.URLConnection#getUseCaches() * @see java.net.URLConnection#setDefaultUseCaches(boolean) */ protected boolean useCaches = defaultUseCaches;
是的,Java
来自sun.net.www.protocol.jar.JarURLConnection
:
JarURLInputStream class extends FilterInputStream { JarURLInputStream(InputStream var2) { super (var2); } public void close() throws IOException { try { super .close(); } finally { if (!JarURLConnection. this .getUseCaches()) { JarURLConnection. this .jarFile.close(); } } } }
如果( 因为 , 因为 ) useCaches
在默认情况下为true
,那么我们感到非常惊讶!
让Java缓存其JAR,但不要破坏我的测试!
JAR缓存可能会提高性能。 但这是否意味着我应该在此之后停止清理-并且在每次测试后都留下杂散的文件?
(当然,我可以说file.deleteOnExit()
;但是由于我正在处理目录层次结构,因此无法保证会按顺序删除内容,并且会保留未删除的目录。)
因此,我想要一种清理JAR缓存的方法–或至少清除我的JAR条目; 完成之后,但在JVM关闭之前。
完全禁用JAR缓存-可能不是一个好主意!
URLConnection
确实提供了一种避免缓存连接条目的选项:
/** * Sets the default value of the <code>useCaches</code> field to the * specified value. * * @param defaultusecaches the new value. * @see #getDefaultUseCaches() */ public void setDefaultUseCaches( boolean defaultusecaches) { defaultUseCaches = defaultusecaches; }
如上所述,如果可以按文件/ URL禁用缓存,那将是完美的。 我们的类加载器在打开JAR时会立即缓存所有条目,因此不再需要再次打开/读取该文件。 但是,一旦打开JAR,就无法禁用缓存。 因此,一旦我们的类加载器打开了JAR,就不会摆脱缓存的文件句柄-直到JVM本身关闭!
URLConnection
还允许您默认禁用所有后续连接的缓存:
/** * Sets the default value of the <code>useCaches</code> field to the * specified value. * * @param defaultusecaches the new value. * @see #getDefaultUseCaches() */ public void setDefaultUseCaches( boolean defaultusecaches) { defaultUseCaches = defaultusecaches; }
但是,如果您一次禁用它,则从那时起,整个JVM可能会受到影响-因为它可能适用于所有基于URLConnection
的实现。 正如我之前说过的那样,这可能会妨碍性能-更不用说使测试脱离启用缓存的实际行为了。
在兔子洞下(再次!):从
侵入性最小的选项是在我知道完成后从缓存中删除我自己的JAR。
好消息是,缓存sun.net.www.protocol.jar.JarFileFactory
已经具有执行该工作的close(JarFile)
方法。
但是可悲的是,缓存类是程序包专用的。 意味着无法在我的测试代码中进行操作。
反思救援!
多亏了反射,我所需要的只是一个小的“桥梁”,它将代表我访问和调用jarFactory.close(jarFile)
:
JarBridge { class JarBridge { static void closeJar(URL url) throws Exception { // JarFileFactory jarFactory = JarFileFactory.getInstance(); Class<?> jarFactoryClazz = Class.forName( "sun.net.www.protocol.jar.JarFileFactory" ); Method getInstance = jarFactoryClazz.getMethod( "getInstance" ); getInstance.setAccessible( true ); Object jarFactory = getInstance.invoke(jarFactoryClazz); // JarFile jarFile = jarFactory.get(url); Method get = jarFactoryClazz.getMethod( "get" , URL. class ); get.setAccessible( true ); Object jarFile = get.invoke(jarFactory, url); // jarFactory.close(jarFile); Method close = jarFactoryClazz.getMethod( "close" , JarFile. class ); close.setAccessible( true ); //noinspection JavaReflectionInvocation close.invoke(jarFactory, jarFile); // jarFile.close(); ((JarFile) jarFile).close(); } }
在测试中,我只需要说:
JarBridge.closeJar(jarPath.toUri().toURL());
就在删除临时目录之前。
那么,要点是什么?
如果您不直接处理JAR文件,那么对您来说无济于事。 但是如果是这样,您可能会遇到这种晦涩的“使用中的文件”错误。 (这对于其他基于URLConnection
的流同样适用。)
如果您碰巧像我那样(不幸),请回想一下,一个臭名昭著的博客写了一些骇人听闻的“ leak dumper”补丁JAR ,可以确切地告诉您JAR(或非JAR)泄漏的位置。
阿迪耶
翻译自: https://www.javacodegeeks.com/2019/10/jar-file-handles-clean-up-after-your-mess.html
JAR文件句柄:烦恼后清理!相关推荐
- 怎么清理句柄_JAR文件句柄:混乱后清理!
怎么清理句柄 在Ultra ESB中,我们使用特殊的热交换类加载器 ,该加载器使我们可以按需重新加载Java类. 这使我们能够从字面上热交换我们的部署单元 -加载,卸载,使用更新的类重新加载,以及正常 ...
- linux 查看文件句柄,查看linux下进程打开的文件句柄数并清理
---查看系统默认的最大文件句柄数,系统默认是1024 # ulimit -n 1024 ----查看当前进程打开了多少句柄数 # lsof -n|awk '{print $2}'|sort|uniq ...
- springboot jar服务器运行后无法请求_Spring Boot微服务中Chaos Monkey的应用
有多少人从未在生产环境中遇到系统崩溃或故障?当然,你们每个人迟早都会经历它.如果我们无法避免失败,那么解决方案似乎是将我们的系统维持在永久性故障状态.Chaos Monkey - 这个概念是Netfl ...
- springboot jar服务器运行后无法请求_Spring boot、微服务、OAuth、OpenID的爱恨情仇!...
在本文中,我们学习如何使用Spring boot轻松配置和部署微服务,然后使用OAuth和OpenID保护它们. 在微服务体系架构中,其中较大的应用程序由多个较小的服务组成,每个服务都有自己的目标,它 ...
- 卸载后清理干净_想要清理你的Mac?选这几款软件就对了
用 macOS 一般不太需要关注垃圾清理和杀毒等问题.但是 Mac 在使用一段时间后,同样可能产生一些无用的文件(如缓存.软件卸载残留等).虽然一般不会对系统造成损害,但是无形中也占用了宝贵的硬盘空间 ...
- linux系统下springboot jar方式启动后允许后台运行
2019独角兽企业重金招聘Python工程师标准>>> springboot 以jar方式打包运行时,ssh客户端或者xshell工具关闭后,程序会自动关闭! 解决方法:编写sh脚本 ...
- 服务器运行jar包日志怎么清理,docker 启动jar包,并将日志文件进行挂载
服务器直接启动jar包命令 打包好的jar包,如果要运行起来,我们可以安装好java环境只会,直接执行java -jar 将服务启动起来 nohup java -server -Xms256m -Xm ...
- tomcat jar包编译后变成文件夹_tomcat学习|tomcat中的类加载器
开头说两句 小刀博客: https://www.lixiang.red 小刀公众号: 程序员学习大本营 学习背景 上期我们聊到了tomcat中各个组件在默认值,在其中,我们看到了有关类加载器的代码, ...
- java jar反编译后保存_java根据jar包反编译后修改再打包回jar的做法
1. 得到一个待要修改的jar包 2. 我的环境是windows,然后解压这个jar包,得到一堆class文件,这时候就找到你需要的那个class文件 3. 我首先是使用jd-gui工具看一下这个cl ...
最新文章
- 牛客题霸 [找到字符串的最长无重复字符子串] C++题解/答案
- 前端学习(2337):angular之管道
- applicationcontext添加配置_让小白也能懂的Bean配置方法
- JavaScript 统计中英混合字符串的长度
- 解决 ThinkPad x270 安装 ubuntu 14.04 后的网络问题
- jq方法中 $(window).load() 与 $(document).ready() 的区别
- Linux内核链表及list_entry解析
- 用python写MapReduce函数——以WordCount为例
- 数学建模:层次分析法实例以及代码
- ansys 命令流学习
- 声网Agora 孙雨润:下一代实时传输体系结构的升级与应用
- IDEA 插件开发实战
- png转icon java,PNG转ICO - steambap的个人空间 - OSCHINA - 中文开源技术交流社区
- 用计算机弹生僻字乐谱,生僻字 E调(拇指琴卡林巴琴弹奏谱)
- 计算机复试专业课笔试,关于计算机考研专业课的考试内容
- 2022 最新微信ipad协议 62 16 扫码登录 wechatapi
- 信息系统项目管理师考点之上午理论知识点总结
- php泥浆护壁,扩孔泥浆护壁式集束式潜孔锤技术
- 【SpringBoot项目实战】之Chrome谷歌浏览器全屏
- 秒数转换为天,小时,分钟,秒的公式