【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
【代码】
包 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编译器错误/警告设置界面中,设置以下两个选项为错误。这样可以在编辑器中确认发生的错误并防止泄漏。默认情况下它们分别设置为警告和忽略。
-
- リソース・リーク
- 潜在的なリソース・リーク
如果是IntelliJ的情况
在文件 -> 设置 -> 编辑器 -> 检查设置中勾选资源管理的所有选项,但对于file.getInputStream的警告未出现,因此需要注意。
然而,如果安装了名为PMD的静态分析插件,就可以检测到(SpotBugs无法检测到)。
看公式API文档
我会积极确认公式API文档中的注意事项。
列出文件的文档。
需要在try-with-resources语句或类似的控制结构中使用此方法,以确保在流操作完成后立即关闭打开的流目录。
MultipartFile.getInputStream的文件摘要
用户有责任关闭返回的流。
监视文件打开数量
即使在IDE或官方文档中有遗漏的情况下,通过实际启动应用程序并监视文件打开数量,我们可以检测到关闭遗漏。
如果是Windows的情况
在Windows11的情况下,你可以从任务管理器的资源监视器中确认。
请注意,如果不按搜索框右侧的更新按钮,屏幕将不会更新。
在使用Files.list时发生了未关闭的情况。
MultipartFile.getInputStream()导致流未关闭的情况
若是Linux系统
在Linux中,我们已经提到过,您可以使用以下命令来查看特定进程打开的文件数。
watch -n 1 -d "ls -l /proc/[プロセスID]/fd | wc -l"
【 参考网站】