【概要】
close
メソッドの実装忘れやtry-with-resources
で実装していない場合、ファイルクローズ漏れとなります。
ファイルクローズ漏れを放置するとファイルディスクリプタの上限を超えてしまい、それ以上ファイルが作成できないエラーが発生することを確認します。
Spring BootでファイルアップロードできるWebAPIを起動し、実際にファイルアップロードして確認していきます。
【環境】
Windows11
Docker version 20.10.21, build baeda1f
Java 19
Spring Boot 3.1.4
【コード】
DemoController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
@RestController
public class DemoController {
@PostMapping("/demo")
public void getDemo(MultipartFile file, int n) throws IOException {
// n = 1~2はファイルクローズ漏れのある実装
// n = 3~4はファイルクローズ漏れのない実装
if (n == 1) {
Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"));
Stream<Path> list = Files.list(tempDir);
} else if (n == 2) {
InputStream stream = file.getInputStream();
} else if (n == 3) {
Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"));
try (Stream<Path> list = Files.list(tempDir)) {
}
} else if (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>
ターミナル2(ファイルディスクリプタの上限変更、アプリ立ち上げ用)
ファイルディスクリプタの上限を確認・変更できる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)
ターミナル3(ファイルオープン数の監視用)
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
ターミナル4(リクエスト実行用)
ファイルアップロードのリクエストを実行します。
テキストファイルについてはそれぞれ下記パターンの確認用となっているので、確認したいコードに合わせて実行してください。
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: No file descriptors available
エラーが発生しています。
エラーメッセージ
2023-10-14T22:17:41.468Z ERROR 124 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]
: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.nio.file.FileSystemException: /tmp: No file descriptors available
at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:100) ~[na:na]
at java.base/sun.nio.fs.UnixException.asIOException(UnixException.java:115) ~[na:na]
at java.base/sun.nio.fs.UnixFileSystemProvider.newDirectoryStream(UnixFileSystemProvider.java:436) ~[na:na]
at java.base/java.nio.file.Files.newDirectoryStream(Files.java:482) ~[na:na]
at java.base/java.nio.file.Files.list(Files.java:3791) ~[na:na]
at com.example.demo.controller.DemoController.getDemo(DemoController.java:22) ~[demo.jar:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:578) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[demo.jar:na]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[demo.jar:na]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[demo.jar:na]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[demo.jar:na]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[demo.jar:na]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[demo.jar:na]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[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]
MultipartFile.getInputStream()
のクローズ漏れをするパターンのエラー
java.io.FileNotFoundException: /tmp/tomcat.8080.6074378283170779966/work/Tomcat/localhost/ROOT/upload_577ee3c3_e912_4314_9526_9ff273979e84_00000034.tmp (No file descriptors available)
エラーが発生しています。
エラーメッセージ
2023-10-14T22:22:34.575Z ERROR 1155 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request] with root cause
java.io.FileNotFoundException: /tmp/tomcat.8080.6074378283170779966/work/Tomcat/localhost/ROOT/upload_577ee3c3_e912_4314_9526_9ff273979e84_00000034.tmp (No file descriptors available)
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.<init>(FileOutputStream.java:236) ~[na:na]
at java.base/java.io.FileOutputStream.<init>(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.<init>(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の場合
ウィンドウ -> 設定 -> Java -> コンパイラー -> エラー/警告 の設定画面で下記2つをエラーに設定します。
そうするとエディターでエラーが発生していることを確認でき、クローズ漏れを防ぐことができます。
デフォルトではそれぞれ警告と無視になっています。
- リソース・リーク
- 潜在的なリソース・リーク
IntelliJの場合
ファイル -> 設定 -> エディター -> インスペクション の設定でリソース管理に全てチェックを入れてもfile.getInputStream
の方は警告が出ませんでしたので注意が必要です。
ただし、PMDという静的解析プラグインを入れると検知できます(SpotBugsでは検知できませんでした)。
公式APIドキュメントを見る
公式APIドキュメントに注意書きが書いてあるので積極的に確認するようにします。
Files.list
のドキュメント
このメソッドは、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"
【参考サイト】