【Java】进行文件未关闭时的行为确认和未关闭文件的防护措施的检验

【摘要】

如果忘记实现close方法或者在try-with-resources语句中没有实现,将会导致文件未关闭的情况。
如果忽略文件未关闭的问题,会导致超过文件描述符上限,进而无法创建更多文件的错误。

我們將啟動一個能夠進行文件上傳的Spring Boot WebAPI,並實際上傳文件以進行確認。

【环境】

Windows11
Docker版本20.10.21,构建baeda1f
Java 19
Spring Boot 3.1.4

Windows11
Docker 20.10.21 版本,构建 baeda1f
Java 19
Spring Boot 3.1.4

【代码】

 

DemoController.javaDemoController.java
包 com.example.demo.controller;

导入 org.springframework.web.bind.annotation.PostMapping;
导入 org.springframework.web.bind.annotation.RestController;
导入 org.springframework.web.multipart.MultipartFile;

导入 java.io.IOException;
导入 java.io.InputStream;
导入 java.nio.file.Files;
导入 java.nio.file.Path;
导入 java.nio.file.Paths;
导入 java.util.stream.Stream;

RestController 类 DemoController {
@PostMapping(“/demo”)
为 public void getDemo(MultipartFile file, int n) throws IOException {
// n = 1~2 是有文件资源未关闭的实现
// n = 3~4 是没有文件资源未关闭的实现
如果 (n == 1) {
Path tempDir = Paths.get(System.getProperty(“java.io.tmpdir”));
Stream list = Files.list(tempDir);
} else 如果 (n == 2) {
InputStream stream = file.getInputStream();
} else 如果 (n == 3) {
Path tempDir = Paths.get(System.getProperty(“java.io.tmpdir”));
try (Stream list = Files.list(tempDir)) {
}
} else 如果 (n == 4) {
try (InputStream stream = file.getInputStream()) {
}
}
}
}

【动作确认】

备好了

请将以下链接中的文件保存到本地。
本文中使用了 C:\work\demo\out\artifacts\demo_jar 作为保存路径进行验证。

 

请打开4个终端(本文中指命令提示符),并按照以下方式执行命令。

终端1(用于创建和启动Docker镜像和容器)

创建并启动Docker镜像和容器。
启动jshell,但保持不做任何操作。

C:\work\demo\out\artifacts\demo_jar>docker build -t file-close-leak .
[+] Building 1.0s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                                        0.0s
 => => transferring dockerfile: 31B                                                         0.0s
 => [internal] load .dockerignore                                                           0.0s
 => => transferring context: 2B                                                             0.0s
 => [internal] load metadata for docker.io/library/eclipse-temurin:19-jdk-alpine            0.9s
 => [1/3] FROM docker.io/library/eclipse-temurin:19-jdk-alpine@sha256:7e4fc4b3ae1bd8ed4205  0.0s
 => [internal] load build context                                                           0.0s
 => => transferring context: 168B                                                           0.0s
 => CACHED [2/3] COPY demo.jar demo.jar                                                     0.0s
 => CACHED [3/3] COPY post-data*.txt /                                                      0.0s
 => exporting to image                                                                      0.0s
 => => exporting layers                                                                     0.0s
 => => writing image sha256:3c22e953d7a40a897d58e1985b76afce23574c9b546c771f471d8d675845fd  0.0s
 => => naming to docker.io/library/file-close-leak                                          0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

C:\work\demo\out\artifacts\demo_jar>
C:\work\demo\out\artifacts\demo_jar>docker run -it -p 8080:8080 --name file-close-leak file-close-leak
Oct 14, 2023 10:10:15 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
|  Welcome to JShell -- Version 19.0.2
|  For an introduction type: /help intro

jshell>

终端二(更改文件描述符上限、用于启动应用)

我将执行ulimit命令来检查和更改文件描述符的上限。
在更改之前,文件描述符的上限为1048576,但为了方便进行行为确认,现在将其改为30。
(请注意,如果文件描述符的更改和应用程序启动不在同一终端中执行,则文件描述符的更改将不会生效)
如果在后续处理中执行请求时出现文件描述符上限错误,可以先关闭应用程序再重新启动,这样文件打开数将被重置。

C:\work\demo\out\artifacts\demo_jar>docker container exec -it file-close-leak /bin/sh
/ # ulimit -n
1048576
/ # ulimit -n 30
/ # ulimit -n
30
/ #
/ # java -jar demo.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.4)

2023-10-14T22:13:03.249Z  INFO 124 --- [           main] com.example.demo.DemoApplication
 : Starting DemoApplication using Java 19.0.2 with PID 124 (/demo.jar started by root in /)
2023-10-14T22:13:03.250Z  INFO 124 --- [           main] com.example.demo.DemoApplication
 : No active profile set, falling back to 1 default profile: "default"
2023-10-14T22:13:03.764Z  INFO 124 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer
 : Tomcat initialized with port(s): 8080 (http)
2023-10-14T22:13:03.770Z  INFO 124 --- [           main] o.apache.catalina.core.StandardService
 : Starting service [Tomcat]
2023-10-14T22:13:03.770Z  INFO 124 --- [           main] o.apache.catalina.core.StandardEngine
 : Starting Servlet engine: [Apache Tomcat/10.1.13]
2023-10-14T22:13:03.831Z  INFO 124 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]
 : Initializing Spring embedded WebApplicationContext
2023-10-14T22:13:03.831Z  INFO 124 --- [           main] w.s.c.ServletWebServerApplicationContext
 : Root WebApplicationContext: initialization completed in 555 ms
2023-10-14T22:13:04.091Z  INFO 124 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer
 : Tomcat started on port(s): 8080 (http) with context path ''
2023-10-14T22:13:04.093Z  INFO 124 --- [           main] com.example.demo.DemoApplication
 : Started DemoApplication in 1.019 seconds (process running for 1.153)

第三终端(用于监视文件打开数目)

监视正在运行的demo.jar进程所打开的文件。由于此处进程ID为124,因此将监视/proc/124/fd目录,但请根据您自己的环境设置进程ID。

C:\work\demo\out\artifacts\demo_jar>docker container exec -it file-close-leak /bin/sh
/ # ps | grep demo.jar | grep -v grep
  124 root      0:05 java -jar demo.jar
/ #
/ # watch -n 1 -d "ls -l /proc/124/fd | wc -l"
Every 1.0s: ls -l /proc/124/fd | wc -l                                       2023-10-14 22:20:06

12

第四终端(用于请求执行)

执行文件上传请求。
对于每个文本文件,都可以根据想要确认的代码选择相应的模式进行执行。
每次执行post-data1.txt和post-data2.txt的请求,终端3的文件打开数都会增加。由于文件描述符上限设置为30,超过30时会在终端2中发生错误。
而对于post-data3.txt和post-data4.txt,不会出现关闭遗漏的情况,所以无论执行多少次,终端3的文件打开数都不会增加。

    • post-data1.txt:Files.listのクローズ漏れをするパターン

 

    • post-data2.txt:MultipartFile.getInputStream()のクローズ漏れをするパターン

 

    • post-data3.txt:Files.listのクローズ漏れをしないパターン

 

    post-data4.txt:MultipartFile.getInputStream()のクローズ漏れをしないパターン
C:\work\demo\out\artifacts\demo_jar>docker container exec -it file-close-leak /bin/sh
/ # wget --header="Content-Type: multipart/form-data; boundary=--------------------------12345" --post-file=post-data1.txt http://localhost:8080/demo > /dev/null 2>&1

【文件描述符超过上限时的错误】

当文件描述符超过上限时,将在终端2中出现错误。

发生Files.list未正确关闭的错误模式

java.nio.file.FileSystemException: 发生了/tmp的文件描述符无可用的错误。

错误消息
2023-10-14T22:17:41.468Z ERROR 124 — [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]
:在路径为[]的上下文中为[dispatcherServlet]的servlet提供服务时抛出异常java.nio.file.FileSystemException:/tmp:没有可用的文件描述符
在java.base / sun.nio.fs.UnixException.translateToIOException(UnixException.java:100)~[na:na]处
在java.base / sun.nio.fs.UnixException.asIOException(UnixException.java:115)~[na:na]处
在java.base / sun.nio.fs.UnixFileSystemProvider.newDirectoryStream(UnixFileSystemProvider.java:436)~[na:na]处
在java.base / java.nio.file.Files.newDirectoryStream(Files.java:482)~[na:na]处
在java.base / java.nio.file.Files.list(Files.java:3791)~[na:na]处
在com.example.demo.controller.DemoController.getDemo(DemoController.java:22)~[demo.jar:na]处
在java.base / jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)~[na:na]处
在java.base / java.lang.reflect.Method.invoke(Method.java:578)~[na:na]处
在org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)~[demo.jar:na]处
在org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)~[demo.jar:na]处
在org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)~[demo.jar:na]处
在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)~[demo.jar:na]处
在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)~[demo.jar:na]处
在org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)~[demo.jar:na]处
在org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)~[demo.jar:na]处
在org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)~[demo.jar:na]处
在org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)~[demo.jar:na]处
在org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)~[demo.jar:na]处
在jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)~[demo.jar:na]处
在org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)~[demo.jar:na]处
在jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)~[demo.jar:na]处
在org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)~[demo.jar:na]处
在org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)~[demo.jar:na]处
在org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)~[demo.jar:na]处
在org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)~[demo.jar:na]处
在org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)~[demo.jar:na]处
在org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)~[demo.jar:na]处
在org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)~[demo.jar:na]处
在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)~[demo.jar:na]处
在org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)~[demo.jar:na]处
在org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)~[demo.jar:na]处
在org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)~[demo.jar:na]处
在org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)~[demo.jar:na]处
在org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)~[demo.jar:na]处
在org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)~[demo.jar:na]处
在org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)~[demo.jar:na]处
在org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)~[demo.jar:na]处
在org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)~[demo.jar:na]处
在org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)~[demo.jar:na]处
在org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740)~[demo.jar:na]处
在org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)~[demo.jar:na]处
在org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)~[demo.jar:na]处
在org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)~[demo.jar:na]处
在org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)~[demo.jar:na]处
在java.base / java.lang.Thread.run(Thread.java:1589)~[na:na]处

在处理MultipartFile.getInputStream()时发生了关闭遗漏的错误。

java.io.FileNotFoundException: /tmp/tomcat.8080.6074378283170779966/work/Tomcat/localhost/ROOT/upload_577ee3c3_e912_4314_9526_9ff273979e84_00000034.tmp(没有可用的文件描述符)发生错误。

错误消息
2023年10月14日T22:22:34.575Z 错误1155 — [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : 在路径为[]的上下文中为servlet [dispatcherServlet]提供的服务时抛出异常[请求处理失败:org.springframework.web.multipart.MultipartException: 解析多部分servlet请求失败],根本原因是java.io.FileNotFoundException: /tmp/tomcat.8080.6074378283170779966/work/Tomcat/localhost/ROOT/upload_577ee3c3_e912_4314_9526_9ff273979e84_00000034.tmp(无可用的文件描述符)
at java.base/java.io.FileOutputStream.open0(Native Method) ~[na:na]
at java.base/java.io.FileOutputStream.open(FileOutputStream.java:295) ~[na:na]
at java.base/java.io.FileOutputStream.(FileOutputStream.java:236) ~[na:na]
at java.base/java.io.FileOutputStream.(FileOutputStream.java:185) ~[na:na]
at org.apache.tomcat.util.http.fileupload.DeferredFileOutputStream.thresholdReached(DeferredFileOutputStream.java:151) ~[demo.jar:na]
at org.apache.tomcat.util.http.fileupload.ThresholdingOutputStream.checkThreshold(ThresholdingOutputStream.java:200) ~[demo.jar:na]
at org.apache.tomcat.util.http.fileupload.ThresholdingOutputStream.write(ThresholdingOutputStream.java:126) ~[demo.jar:na]
at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:103) ~[demo.jar:na]
at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:292) ~[demo.jar:na]
at org.apache.catalina.connector.Request.parseParts(Request.java:2788) ~[demo.jar:na]
at org.apache.catalina.connector.Request.getParts(Request.java:2689) ~[demo.jar:na]
at org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:774) ~[demo.jar:na]
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:93) ~[demo.jar:na]
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.(StandardMultipartHttpServletRequest.java:86) ~[demo.jar:na]
at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:112) ~[demo.jar:na]
at org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1219) ~[demo.jar:na]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1053) ~[demo.jar:na]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[demo.jar:na]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[demo.jar:na]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[demo.jar:na]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[demo.jar:na]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[demo.jar:na]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[demo.jar:na]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[demo.jar:na]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[demo.jar:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[demo.jar:na]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[demo.jar:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[demo.jar:na]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[demo.jar:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[demo.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[demo.jar:na]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[demo.jar:na]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[demo.jar:na]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[demo.jar:na]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[demo.jar:na]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[demo.jar:na]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[demo.jar:na]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) ~[demo.jar:na]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[demo.jar:na]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[demo.jar:na]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[demo.jar:na]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) ~[demo.jar:na]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[demo.jar:na]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[demo.jar:na]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[demo.jar:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[demo.jar:na]
at java.base/java.lang.Thread.run(Thread.java:1589) ~[na:na]

【防止信息泄漏措施】

正确设置IDE的警告,以免忽略掉。

在Eclipse中

在Windows 配置面板的Java编译器错误/警告设置界面中,设置以下两个选项为错误。这样可以在编辑器中确认发生的错误并防止泄漏。默认情况下它们分别设置为警告和忽略。

    • リソース・リーク

 

    潜在的なリソース・リーク
image.png
image.png
如果是IntelliJ的情况

在文件 -> 设置 -> 编辑器 -> 检查设置中勾选资源管理的所有选项,但对于file.getInputStream的警告未出现,因此需要注意。

image.png
image.png

然而,如果安装了名为PMD的静态分析插件,就可以检测到(SpotBugs无法检测到)。

image.png

看公式API文档

我会积极确认公式API文档中的注意事项。

列出文件的文档。

 

需要在try-with-resources语句或类似的控制结构中使用此方法,以确保在流操作完成后立即关闭打开的流目录。

MultipartFile.getInputStream的文件摘要

 

用户有责任关闭返回的流。

监视文件打开数量

即使在IDE或官方文档中有遗漏的情况下,通过实际启动应用程序并监视文件打开数量,我们可以检测到关闭遗漏。

如果是Windows的情况

在Windows11的情况下,你可以从任务管理器的资源监视器中确认。
请注意,如果不按搜索框右侧的更新按钮,屏幕将不会更新。

在使用Files.list时发生了未关闭的情况。
image.png
MultipartFile.getInputStream()导致流未关闭的情况
image.png
若是Linux系统

在Linux中,我们已经提到过,您可以使用以下命令来查看特定进程打开的文件数。

watch -n 1 -d "ls -l /proc/[プロセスID]/fd | wc -l"

【 参考网站】

 

广告
将在 10 秒后关闭
bannerAds