我们的一个应用程序泄漏了文件句柄,但我们尚未找到原因。
在代码中,我可以看到几个与此类似的函数:
public ResponseEntity<InputStreamResource> getFoo( ... ) {
InputStream content = getContent(...)
InputStreamResource isr = new InputStreamResource(content);
return ResponseEntity.status(HttpServletResponse.SC_OK).body(isr);
}
(为简便起见,{if
检查并删除了try
/ catch
)
我确信这部分会导致问题,因为当我使用JMeter对特定代码进行负载测试时,我可以看到getContent()
在此阶段失败:
is = Files.newInputStream(f.toPath());
通常我会关闭InputStream
,但是由于这段简短的代码,我无法在return
或调用body
之前关闭流。
当我运行lsof
(代码在Linux上运行)时,我可以看到成千上万的文件以读取模式打开。因此,我确定此问题是由流未关闭引起的。
我应该使用最佳实践代码进行交易吗?
答案 0 :(得分:5)
您可以尝试使用StreamingResponseBody
StreamingResponseBody
一种用于异步请求处理的控制器方法返回值类型,其中应用程序可以直接写入响应OutputStream,而无需占用Servlet容器线程。
由于您是在一个单独的线程上直接写响应,因此在解决close()
之前要调用return
的问题。
可能您可以从以下示例开始
public ResponseEntity<StreamingResponseBody> export(...) throws FileNotFoundException {
//...
InputStream inputStream = new FileInputStream(new File("/path/to/example/file"));
StreamingResponseBody responseBody = outputStream -> {
int numberOfBytesToWrite;
byte[] data = new byte[1024];
while ((numberOfBytesToWrite = inputStream.read(data, 0, data.length)) != -1) {
System.out.println("Writing some bytes..");
outputStream.write(data, 0, numberOfBytesToWrite);
}
inputStream.close();
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=generic_file_name.bin")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
}
此外,除了使用InputStream之外,您还可以尝试使用Files
(自Java 7开始)
因此您不必管理InputStream
File file = new File("/path/to/example/file");
StreamingResponseBody responseBody = outputStream -> {
Files.copy(file.toPath(), outputStream);
};
答案 1 :(得分:3)
您可以重构读取本地文件并将其内容设置为HTTP响应正文的所有控制器方法:
您无需使用ResponseEntity
方法,而是注入基础的HttpServletResponse
并将从getContent(...)
方法返回的输入流的字节复制到HttpServletResponse
的输出流,例如通过使用Apache CommonsIO或Google Guava库的与IO相关的实用程序方法。无论如何,请确保关闭输入流!下面的代码通过使用“ try-with-resources”语句隐式地执行此操作,该语句在语句末尾关闭已声明的输入流。
@RequestMapping(value="/foo", method=RequestMethod.GET)
public void getFoo(HttpServletResponse response) {
// use Java7+ try-with-resources
try (InputStream content = getContent(...)) {
// if needed set content type and attachment header
response.addHeader("Content-disposition", "attachment;filename=foo.txt");
response.setContentType("txt/plain");
// copy content stream to the HttpServletResponse's output stream
IOUtils.copy(myStream, response.getOutputStream());
response.flushBuffer();
}
}
参考:
https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html https://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html https://google.github.io/guava/releases/19.0/api/docs/com/google/common/io/ByteStreams.html https://commons.apache.org/proper/commons-io/javadocs/api-release/index.html
(尤其是研究类public static int copy(InputStream input, OutputStream output) throws IOException
的方法public static int copyLarge(InputStream input, OutputStream output) throws IOException
和org.apache.commons.io.IOUtils
)
答案 2 :(得分:2)
假设您使用的是Spring,则您的方法可以返回Resource并让Spring处理其余部分(包括关闭基础流)。有few implementations of Resource are available within Spring API,否则您需要实现自己的。最后,您的方法将变得简单,并且需要下面的内容
public ResponseEntity<Resource> getFo0(...) {
return new InputStreamResource(<Your input stream>);
}
答案 3 :(得分:0)
由于此InputStream
基本上来自一个简单的文件,因此此代码可以很好地替代:
FileSystemResource fsr = new FileSystemResource(fileName);
return ResponseEntity.status(HttpServletResponse.SC_OK).body(fsr);
FileSystemResource
可以使用java.util.File
,java.nio.file.Path
甚至是String
指向相关文件。