【问题标题】:downloading large file throws OutOfMemoryError in production env下载大文件会在生产环境中引发 OutOfMemoryError
【发布时间】:2018-08-28 03:26:10
【问题描述】:

环境 jhipster,角度 5, 弹簧靴

我的应用程序具有上传和下载文件功能。文件作为二进制 blob 存储在 db 中。

我可以下载小文件。但是,大文件(例如 35M)会出现此错误。 (这些大文件是应用上传的)

根据堆栈跟踪,java.util.Arrays.copyOf 正在引发错误。 hibernate 将此数组称为 fn。我假设尝试将 35M 的二进制数据(以 blob 形式)放置在可以容纳 2M 左右的数组中。

是否有解决方法或解决方法来处理大数据?我们可以告诉hibernate将数据分块吗?

通过堆栈跟踪扫描还给出了指向 com.neemshade.sniper.security.jwt.JWTFilter.doFilter(JWTFilter.java:36) 的指针 第36行是

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String jwt = resolveToken(httpServletRequest);
        if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {
            Authentication authentication = this.tokenProvider.getAuthentication(jwt);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(servletRequest, servletResponse);  <-- this code
    }

如果这个过滤器是错误的根源,它会是休眠问题吗?

我的代码说明。 afterDownload() 调用服务器 api 下载二进制文件。一旦数据可用,将调用 saveToLocal() 以弹出“另存为”对话框以将二进制数据存储在文件中。

这是我的客户端代码:

afterDownload(event) {
      this.pending = true;

      this.extTaskService.downloadFiles(this.source, this.id, this.selectedTasks)
      .subscribe(
        (data) => {
          this.jhiAlertService.success('success! downloaded files. ');
          console.log(data);
          this.saveToLocal(data);
          this.uponCompletion(true);
          this.pending = false;
        },
        (err) => {
          this.jhiAlertService.error('error in download! ' + err.message, null, null);
          console.log(JSON.stringify(err));
          this.pending = false;
        },
        () => this.jhiAlertService.success('downloaded files', null, null)
      );
    }

  saveToLocal(response) {
    console.log('inside saveToLocal');
    const ieEDGE = navigator.userAgent.match(/Edge/g);
    const ie = navigator.userAgent.match(/.NET/g); // IE 11+
    const oldIE = navigator.userAgent.match(/MSIE/g);

    const blob = new Blob([response], { type: 'application/octet-stream'});
    const fileName = 'files.zip';

    if (ie || oldIE || ieEDGE) {
      console.log('got to ie');
      window.navigator.msSaveBlob(blob, fileName);
    } else {
      const reader = new FileReader();
      reader.onloadend = function() {
        console.log('onloadend');
        window.location.href = reader.result;
      };
      console.log('readAsDataURL');
      reader.readAsDataURL(blob);
    }
  }

客户服务:

downloadFiles(source: string, id: number, selectedTasks: Task[]): Observable<any> {
    const finalUrl: string = this.resourceUrl + 'download-files/' + source + '/' + id + '/' +
           (selectedIds == null || selectedIds === '' ? '0' : selectedIds);
    return this.http.get(finalUrl, { responseType: 'blob' });
  }

服务器端: extDownloaderService.downloadFiles() 准备输出二进制数据。我将小块的数据刷新到响应中。

@GetMapping(value="download-files/{source}/{id}/{selectedIds}", produces="application/zip")
    @ResponseBody
    public void downloadFiles(
            @PathVariable String source, @PathVariable(value = "id") Long id,
            @PathVariable(value = "selectedIds") String selectedIds, HttpServletResponse response) throws Exception {

        byte[] bytes = extDownloaderService.downloadFiles(source, id, selectedIds);

//      headers.add("Content-Type", "application/octet-stream");
        response.setHeader("Content-Type", "application/zip");
        response.setHeader("Content-Disposition", "attachment; filename=\"files.zip\"");

        OutputStream os = response.getOutputStream();

        try
        {
            ByteArrayInputStream byteIs = new ByteArrayInputStream(bytes);


            byte[] buf=new byte[8192];
            int bytesread = 0, bytesBuffered = 0;
            while( (bytesread = byteIs.read( buf )) > -1 ) {
                os.write( buf, 0, bytesread );
                bytesBuffered += bytesread;
                if (bytesBuffered > 1024 * 1024) { //flush after 1MB
                    bytesBuffered = 0;
                    os.flush();
                }
            }
        }
        finally {
            if (os != null) {
                os.flush();
            }
        }

        os.close();

//      return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
       }

当使用大文件运行应用程序时,我得到了这个堆栈跟踪。

@400000005aaeaf751478b324 原因:java.lang.OutOfMemoryError: Java 堆空间@400000005aaeaf751478baf4 在 java.util.Arrays.copyOf(java.base@9-internal/Arrays.java:3744) @400000005aaeaf751478bedc 在 java.lang.AbstractStringBuilder.ensureCapacityInternal(java.base@9-internal/AbstractStringBuilder.java:146) @400000005aaeaf751478bedc 在 java.lang.AbstractStringBuilder.append(java.base@9-internal/AbstractStringBuilder.java:510) @400000005aaeaf751478c2c4 在 java.lang.StringBuilder.append(java.base@9-internal/StringBuilder.java:141) @400000005aaeaf751478ca94 在 java.util.Arrays.toString(java.base@9-internal/Arrays.java:4958) @400000005aaeaf751478ca94 在 org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor.extractLoggableRepresentation(PrimitiveByteArrayTypeDescriptor.java:63) @400000005aaeaf751478ce7c 在 org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor.extractLoggableRepresentation(PrimitiveByteArrayTypeDescriptor.java:26) @400000005aaeaf751478de1c 在 org.hibernate.type.AbstractStandardBasicType.toLoggableString(AbstractStandardBasicType.java:296) @400000005aaeaf751478e204 在 org.hibernate.type.TypeHelper.toLoggableString(TypeHelper.java:439) @400000005aaeaf751478e204 在 org.hibernate.cache.spi.entry.StandardCacheEntryImpl.(StandardCacheEntryImpl.java:60) @400000005aaeaf751478e9d4 在 org.hibernate.persister.entity.AbstractEntityPersister$StandardCacheEntryHelper.buildCacheEntry(AbstractEntityPersister.java:5307) @400000005aaeaf751478edbc 在 org.hibernate.persister.entity.AbstractEntityPersister.buildCacheEntry(AbstractEntityPersister.java:4307) @400000005aaeaf751478edbc 在 org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:196) @400000005aaeaf751478f58c 在 org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:125) @400000005aaeaf751478f58c 在 org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:238) @400000005aaeaf751478f974 在 org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:209) @400000005aaeaf751478fd5c 在 org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133) @400000005aaeaf7514790cfc 在 org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122) @400000005aaeaf7514790cfc 在 org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86) @400000005aaeaf75147910e4 在 org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167) @400000005aaeaf75147918b4 在 org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4083) @400000005aaeaf7514791c9c 在 org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508) @400000005aaeaf7514791c9c 在 org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478) @400000005aaeaf751479246c 在 org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219) @400000005aaeaf751479246c 在 org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278) @400000005aaeaf7514792854 在 org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121) @400000005aaeaf75147937f4 在 org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89) @400000005aaeaf7514793bdc 在 org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239) @400000005aaeaf7514793bdc 在 org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1122) @400000005aaeaf7514793fc4 在 org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:646) @400000005aaeaf7514794f64 在 org.hibernate.type.EntityType.resolve(EntityType.java:431) @400000005aaeaf7514794f64 在 org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:165)

任何帮助表示赞赏。

【问题讨论】:

  • 不要将文件读入内存。而是直接将其流式传输到OutputStream。`
  • 二进制数据来自db。它存储为 blob。我正在接受并流式传输响应
  • @M.Deinum,有没有办法从数据库中获取 blob 字段作为流?

标签: angular hibernate spring-boot jhipster


【解决方案1】:

我不确定您是否尝试过,但请尝试以下解决方案,

解决方案 - 1:您允许上传文件的最大大小,在您的配置文件中提及。如果未定义或尺寸较小,则增加它(根据您的要求设置)。

#Multipart
spring.http.multipart.enable=true
spring.servlet.multipart.max-request-size=50MB
spring.servlet.multipart.max-file-size=50MB

解决方案 - 2:如果您使用的是 Spring Boot,那么它为您提供了将文件作为资源发送的便利。所以试试下面的方法,

Path path = Paths.get(<directory-path>).normalize();
Path filePath = path.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
Return ResponseEntity.ok
        .contentType(MediaType.<MIME-Type>)
        .header(HttpHeaders.CONTENT_DISPOSITION, “attachment; filename=””+ resource.filename())
        .body(resource);

此方法的返回类型:ResponseEntity&lt;Resource&gt;

解决方案 - 3:在您上传代码的服务器或系统上,不允许您上传大文件,您的服务器存在一些权限或限制,因此请与您联系系统支持团队(我对此并不清楚,因为我至少在 0.6 年前就这样做了)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-13
    • 2019-07-24
    • 2017-08-14
    • 2018-03-27
    • 1970-01-01
    • 2015-12-12
    • 2021-03-21
    • 2019-08-06
    相关资源
    最近更新 更多