私はユーザーがファイルをダウンロードできるSpring-MVCアプリケーションに取り組んでいます。ユーザーは、ダウンロードメカニズムをトリガーする添付ファイルをクリックすることができます。Spring、Java:メモリ不足を避けるためのストリーミングファイルのダウンロード
昨日、複数のダウンロードと2つのファイルに約2 GBのファイルがあると、メモリ不足エラー(以下のログ)が発生しました。
この問題を回避するには、ダウンロードデータをチャンクでストリーミングし、ファイル全体ではなくサービスレイヤでのみチャンクを処理するような方法が考えられます。
残念ながら、私はこれに先立って移動する方法がわかりません。どんな助けもいいと思います。このオプションが飛べない場合、この問題の解決方法に関する推奨事項。
エラーログ:
HTTP Status 500 - Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory
type Exception report
message Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory
description The server encountered an internal error that prevented it from fulfilling this request.
exception
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory
org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletionWithError(DispatcherServlet.java:1303)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:977)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
コントローラーコード:
@RequestMapping(value = "/download/attachment/{attachid}", method = RequestMethod.GET)
public void getAttachmentFromDatabase(@PathVariable("attachid") int attachid,
, HttpServletResponse response,) {
response.setContentType("application/octet-stream");
GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmenById(attachid);
response.setHeader("Content-Disposition", "attachment; filename=\"" + groupAttachments.getFileName() + "\"");
response.setContentLength(groupAttachments.getSendAttachment().length);
FileCopyUtils.copy(groupAttachments.getSendAttachment(), response.getOutputStream());
response.flushBuffer();
}
サービス層:
@Override
public GroupAttachments getAttachmenById(int attachId) {
Person person = this.personService.getCurrentlyAuthenticatedUser();
GroupAttachments groupAttachments = this.groupAttachmentsDAO.getAttachmenById(attachId);
GroupMembers groupMembers = this.groupMembersService.returnMembersMatchingUsernameAccountId(person.getUsername(),
groupAttachments.getGroupId());
if (!(groupMembers == null)) {
if (person.getUsername().equals(groupMembers.getMemberUsername())) {
try {
Path path = Paths.get(msg + groupAttachments.getGroupId() + "/" +
groupAttachments.getFileIdentifier());
groupAttachments.setSendAttachment(Files.readAllBytes(path));
return groupAttachments;
} catch (IOException ignored) {
this.groupAttachmentsDAO.removeAttachment(attachId);
return null;
}
}
return null;
} else {
return null;
}
}
ありがとうございます。 :-)
更新
新しいダウンロードメカニズム:
コントローラー:
public ResponseEntity<byte[]> getAttachmentFromDatabase(@PathVariable("attachid") int attachid,
@PathVariable("groupaccountid") Long groupAccountId, @PathVariable("api") String api,
HttpServletResponse response,
@PathVariable("type") boolean type) {
Path path = this.groupAttachmentsService.getAttachmentPathById(attachid);
GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmentObjectOnlyById(attachid);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\""+groupAttachments.getFileName()+"\"");
try {
OutputStream outputStream = response.getOutputStream();
Files.copy(path,outputStream);
outputStream.flush();
outputStream.close();
response.flushBuffer();
}
サービス層:あなたのサービスで全体のコンテンツをロードする
@Override
public Path getAttachmentPathById(int attachId){
Person person = this.personService.getCurrentlyAuthenticatedUser();
GroupAttachments groupAttachments = this.groupAttachmentsDAO.getAttachmenById(attachId);
GroupMembers groupMembers = this.groupMembersService.returnMembersMatchingUsernameAccountId(person.getUsername(),
groupAttachments.getGroupId());
if (!(groupMembers == null)) {
if (person.getUsername().equals(groupMembers.getMemberUsername())) {
try {
return Paths.get(msg + groupAttachments.getGroupId() + "/" +
groupAttachments.getFileIdentifier());
} catch (Exception ignored) {
return null;
}
}
return null;
} else {
return null;
}
}
すべてをメモリに読み込んでいますが、しないでください。 'Files.readAllBytes'は' 2Gb'全体をメモリに読み込みます。単にしないでください。読み込みを遅らせ、最初にロードするのではなく、読み込んだ部分を直接読み込みます。代わりに 'Files.copy(Path、OutputStream)'を使用してください。 –
コピーは、それをロードする代わりにストリーミングします。コメントをお読みください... –
@ M.Deinum:誤読にごめんね。私はコードを更新し、またFiles.copy(path、outputStream)を試してみました。うまく動作します。私はメインポストに更新としてコードを掲載しました。可能であれば、最適化が可能かどうかチェックしてください。ありがとうございました。 –