处理Hutool的Http工具上传大文件报OOM
程序环境
- JDK版本: 1.8
- Hutool版本: 5.8.25
问题描述
客服端文件上传主要代码:
HttpRequest httpRequest = HttpUtil.createPost(FILE_UPLOAD_URL);
Resource urlResource = new UrlResource(url, fileName);
httpRequest.form("file", urlResource);
HttpResponse httpResponse = httpRequest.execute();
大文件上传 java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3236) ~[na:1.8.0_275]at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118) ~[na:1.8.0_275]at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93) ~[na:1.8.0_275]at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:135) ~[na:1.8.0_275]at sun.net.www.http.PosterOutputStream.write(PosterOutputStream.java:63) ~[na:1.8.0_275]at cn.hutool.http.MultipartOutputStream.write(MultipartOutputStream.java:108) ~[hutool-all-5.8.25.jar!/:5.8.25]at java.io.OutputStream.write(OutputStream.java:116) ~[na:1.8.0_275]at cn.hutool.core.io.copy.StreamCopier.doCopy(StreamCopier.java:102) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.core.io.copy.StreamCopier.copy(StreamCopier.java:68) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.core.io.IoUtil.copy(IoUtil.java:162) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.core.io.IoUtil.copy(IoUtil.java:146) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.core.io.IoUtil.copy(IoUtil.java:132) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.core.io.IoUtil.copy(IoUtil.java:119) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.core.io.resource.Resource.writeTo(Resource.java:76) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.MultipartOutputStream.appendResource(MultipartOutputStream.java:163) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.MultipartOutputStream.write(MultipartOutputStream.java:96) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.body.MultipartBody$$Lambda$2190/568941495.accept(Unknown Source) ~[na:na]at cn.hutool.core.map.TableMap.forEach(TableMap.java:253) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.body.MultipartBody.write(MultipartBody.java:78) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.body.RequestBody.writeClose(RequestBody.java:27) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.HttpRequest.sendMultipart(HttpRequest.java:1402) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.HttpRequest.send(HttpRequest.java:1340) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.HttpRequest.doExecute(HttpRequest.java:1188) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.HttpRequest.execute(HttpRequest.java:1051) ~[hutool-all-5.8.25.jar!/:5.8.25]at cn.hutool.http.HttpRequest.execute(HttpRequest.java:1027) ~[hutool-all-5.8.25.jar!/:5.8.25]at com.mbzj.ai.third.RhzClient.execute(RhzClient.java:270) ~[classes!/:1.0-SNAPSHOT]at com.mbzj.ai.third.RhzClient.uploadKnowledgeFile(RhzClient.java:184) ~[classes!/:1.0-SNAPSHOT]at com.mbzj.ai.third.RhzService.uploadKnowledgeFile(RhzService.java:132) ~[classes!/:1.0-SNAPSHOT]at com.mbzj.ai.listener.KnowledgeFileListener.handleAddKnowledgeFileEvent(KnowledgeFileListener.java:64) ~[classes!/:1.0-SNAPSHOT]at com.mbzj.ai.listener.KnowledgeFileListener$$FastClassBySpringCGLIB$$beafef7e.invoke(<generated>) ~[classes!/:1.0-SNAPSHOT]at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.28.jar!/:5.3.28]at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793) ~[spring-aop-5.3.28.jar!/:5.3.28]
分析问题
从异常堆栈信息中可以看出这里使用了 java.io.ByteArrayOutputStream 。实际上就是把文件全部都加载到了Byte数组中,如果上传的文件过大必定会导致OOM。
hutool httpRequest执行流程
这里实际上是使用的 java.net.HttpURLConnection。
解决方案
java.net.HttpURLConnection 是支持 StreamingMode 传输HTTP请求的,有两种方式开启:
- setFixedLengthStreamingMode
当预先知道内容长度时,该方法用于使得能够在没有内部缓冲的情况下流式传输HTTP请求主体。
如果应用程序尝试写入比指示的content-length更多的数据,或者如果应用程序在写入指示的数量之前关闭OutputStream,则将引发异常。 - setChunkedStreamingMode
当内容长度为不提前知道。在这种模式下,使用分块传输编码来发送请求正文。请注意,并非所有HTTP服务器都支持此模式。
启用输出流时,无法自动处理身份验证和重定向。如果需要身份验证或重定向,则读取响应时将引发HttpRetryException。
Hutool 的 HttpRequest中只提供了 setChunkedStreamingMode方式,setFixedLengthStreamingMode 方式其实感觉上会更好,不会出现服务端不支持的情况,作者表示下一版本中将会支持setFixedLengthStreamingMode 。
先来测试一下 setChunkedStreamingMode 的效果。
这里自己写一个服务端的接口看看StreamingMode的header有什么区别。
@PostMapping("test")
public void test(MultipartFile file, HttpServletRequest request) {System.out.println("fileSize" + file.getSize());// 打印所有headerEnumeration<String> headerNames = request.getHeaderNames();while (headerNames.hasMoreElements()) {String name = headerNames.nextElement();System.out.println(name + ":" + request.getHeader(name));}
}
这是修改前会出现OOM的客户端代码
HttpRequest httpRequest = HttpUtil.createPost("http://127.0.0.1:8064/test");
URL fileUrl = new URL("https://xxxx/1a67c727f8a845dd8b0b9825026349dd.mp4");
UrlResource urlResource = new UrlResource(fileUrl, "test.mp4");
httpRequest.form("file", urlResource);
System.out.println(httpRequest);
HttpResponse httpResponse = httpRequest.execute();
System.out.println(httpResponse);
堆内存明显增高
服务端日志输出:
accept:text/html,application/json,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Hutool
accept-encoding:gzip, deflate
content-type:multipart/form-data; boundary=--------------------Hutool_rV0KKNQCkTkwywrQ
cache-control:no-cache
pragma:no-cache
host:127.0.0.1:8064
connection:keep-alive
content-length:128553150
客户端上传日志:
Request Url: http://127.0.0.1:8064/ai/knowledge/test
Request Headers: Accept: text/html,application/json,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 HutoolAccept-Encoding: gzip, deflate
Request Body: file=https%3A%2F%2Fcos-uclass.lconrise.cn%2Fbiz%2Fai%2Fknowledge%2Ffile%2F1a67c727f8a845dd8b0b9825026349dd.mp4Response Headers: Keep-Alive=[timeout=60]X-Frame-Options=[DENY]null=[HTTP/1.1 200]Cache-Control=[no-cache, no-store, max-age=0, must-revalidate]X-Content-Type-Options=[nosniff]Connection=[keep-alive]Expires=[0]Pragma=[no-cache]Content-Length=[0]X-XSS-Protection=[1; mode=block]Date=[Wed, 11 Sep 2024 01:59:55 GMT]
Response Body:
客户端通过 setChunkedStreamingMode 开启 StreamingMode:
HttpRequest httpRequest = HttpUtil.createPost("http://127.0.0.1:8064/ai/knowledge/test");
URL fileUrl = new URL("https://cos-uclass.lconrise.cn/biz/ai/knowledge/file/1a67c727f8a845dd8b0b9825026349dd.mp4");
UrlResource urlResource = new UrlResource(fileUrl, "test.mp4");
httpRequest.form("file", urlResource);
httpRequest.setChunkedStreamingMode(1024 * 8);
System.out.println(httpRequest);
HttpResponse httpResponse = httpRequest.execute();
System.out.println(httpResponse);
上传文件时堆内存无明细变化:
服务端日志输出:
accept:text/html,application/json,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Hutool
accept-encoding:gzip, deflate
content-type:multipart/form-data; boundary=--------------------Hutool_Zn5eac5m74pQH1IJ
cache-control:no-cache
pragma:no-cache
host:127.0.0.1:8064
connection:keep-alive
transfer-encoding:chunked
客户端上传日志:
Request Url: http://127.0.0.1:8064/ai/knowledge/test
Request Headers: Accept: text/html,application/json,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 HutoolAccept-Encoding: gzip, deflate
Request Body: file=https%3A%2F%2Fcos-uclass.lconrise.cn%2Fbiz%2Fai%2Fknowledge%2Ffile%2F1a67c727f8a845dd8b0b9825026349dd.mp4Response Headers: Keep-Alive=[timeout=60]X-Frame-Options=[DENY]null=[HTTP/1.1 200]Cache-Control=[no-cache, no-store, max-age=0, must-revalidate]X-Content-Type-Options=[nosniff]Connection=[keep-alive]Expires=[0]Pragma=[no-cache]Content-Length=[0]X-XSS-Protection=[1; mode=block]Date=[Wed, 11 Sep 2024 02:02:28 GMT]
Response Body:
正常上传请求包含 content-lengt
header, 来告诉服务端当前请求主体内容的字节数。
StreamingMode
中 没有 content-length
,而是新增了 transfer-encoding:chunked
。
扩展
-
Transfer-Encoding: chunked:
- 这是一种 HTTP 传输编码,允许服务器在不知道整个响应内容长度的情况下,分批次发送数据。
- 每个数据块前会有一个指定大小的头部,表明该块的大小,直到遇到大小为
0
的块,表示传输结束。
-
服务端处理:
- 服务端(如 Tomcat)在接收到
chunked
编码的请求时,会按照分块传输编码的规则来读取数据。 - 服务端会持续读取数据块,直到检测到一个大小为
0
的块,这表示输入流已经结束。
- 服务端(如 Tomcat)在接收到
-
Tomcat 配置:
- Tomcat 允许通过配置
<Connector>
标签的maxPostSize
属性来限制请求体的最大大小。 fileSizeThreshold
参数定义了上传文件写入磁盘的阈值,这对于处理大文件上传尤为重要。
- Tomcat 允许通过配置
-
流式上传:
- Tomcat 支持流式上传,这意味着数据可以边读边写,不需要将整个文件内容一次性加载到内存中。
- 流式上传适用于大文件或实时数据传输,如视频流。
-
异步处理:
- Tomcat 支持 Servlet 3.0 规范中的异步处理机制,允许长时间运行的操作在单独的线程中执行。
- 这可以提高 Tomcat 的并发处理能力和系统吞吐量。
-
异常处理:
- 在文件上传过程中,如果出现异常(如文件大小超出限制),Tomcat 会抛出相应的异常。
- 开发者需要在代码中妥善处理这些异常,并在必要时进行异常捕获和处理。
-
请求结束:
- 处理完所有数据块后,Tomcat 会关闭输入流,并根据请求的内容执行相应的业务逻辑。
用了这么久HTTP, 你是否了解Content-Length和Transfer-Encoding
用了这么久HTTP, 你是否了解Content-Length和Transfer-Encoding
HTTP响应字段Transfer-Encoding含义及作用详解
相关文章:

处理Hutool的Http工具上传大文件报OOM
程序环境 JDK版本: 1.8Hutool版本: 5.8.25 问题描述 客服端文件上传主要代码: HttpRequest httpRequest HttpUtil.createPost(FILE_UPLOAD_URL); Resource urlResource new UrlResource(url, fileName); httpRequest.form("file&q…...

transforms的使用
示例代码 from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms#打开该图片 img_path"hymenoptera_data/val/bees/10870992_eebeeb3a12.jpg" imgImage.open(img_path) writerSummaryWriter("logs&quo…...

python-PyQt项目实战案例:制作一个视频播放器
文章目录 1. 关键问题描述2. 通过OpenCV读取视频/打开摄像头抓取视频3. 通过PyQt 中的 QTimer定时器实现视频播放4. PyQt 视频播放器实现代码参考文献 1. 关键问题描述 在前面的文章中已经分享了pyqt制作图像处理工具的文章,也知道pyqt通过使用label控件显示图像的…...

反向传播的微积分原理 | Chapter 4 | Deep Learning | 3Blue1Brown
目录 前言1. 简介2. 神经网络中的链式法则3. 微积分的计算4. 公式含义5. 代价函数对权重偏置的敏感度6. 多个神经元的情形7. 回顾相关资料结语 前言 3Blue1Brown 视频笔记,仅供自己参考 这个章节主要来深度讲解反向传播中的一些微积分理论 官网:https://…...
matlab读取excel表格
使用matlab读取excel表格中的数据 使用推荐代码读取excel表格中的数据 path "C:\Users\24975\Desktop\503\GUI展示案例\Tx_20_0_Rx_40_90_0.1_95_L.xlsx";%文件路径 data readtable(path,Sheet,Sheet1,ReadRowNames,false,ReadVariableNames,false,Ra…...

基于springboot+vue实现的助学兼职系统(源码+L文+ppt)4-092
基于springbootvue实现的助学兼职系统(源码L文ppt)4-092 第4章 系统设计 4.1 总体功能设计 一般学生、招聘公司和管理者都需要登录才能进入助学兼职系统,使用者登录时会在后台判断使用的权限类型,包括一般使用者和管理者,一般使…...

⌈ 传知代码 ⌋ 农作物病害分类(Web端实现)
💛前情提要💛 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间,对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…...

CMU生成式人工智能大模型:从入门到放弃(九)
引言 在前面的系列博客中,我们深入探讨了生成式对抗网络(GANs)和变分自编码器(VAEs)等生成式模型。今天,我们将探索扩散模型(Diffusion Models)的进一步应用,并讨论在上…...
HTML基础总结
一、简介 HTML(HyperText Markup Language)即超文本标记语言,是用于创建网页的标准标记语言。它通过使用各种标签来定义网页的结构和内容,告诉浏览器如何显示网页。HTML 文档由标签和文本组成,标签用于描述文本的性质…...

EXCELL中如何两条线画入一张图中,标记坐标轴标题?
1,打开excel,左击选中两列, 2,菜单栏>“插入”>”二维折线图”选中一个 3,选中出现的两条线中的一条右击>最下一行,“设置数据系列格式” 4,右测“系列选项中”>点击“次坐标轴” 5…...

Zabbix企业级分布式监控环境部署
“运筹帷幄之中,决胜千里之外”。在IT运维中,监控占据着重要的地位,按比例来算,说占30%一点也不为过。对IT运维工程师来说,构建一个真正可用的监控告警系统是一项艰巨的任务。在监控系统的开源软件中,可供选…...

水轮发电机油压自动化控制系统解决方案介绍
在现代水电工程中,水轮机组油压自动化控制系统,不仅直接关系到水轮发电机组的安全稳定运行,还影响着整个水电站的生产效率和经济效益。 一、系统概述 国科JSF油压自动控制系统,适用于水轮发电机组调速器油压及主阀(蝶…...

今天不分享技术,分享秋天的故事
引言 这个爱情故事好像是个悲剧,你说的是婚姻。爱情没有悲剧,对爱者而言,爱情怎么会是悲剧呢。对春天而言,秋天是它的悲剧吗。结尾是什么,等待,之后呢,没有之后。或者说,等待的结果…...

转录组上游分析流程(三)
环境部署——数据下载——查看数据(非质控)——数据质控——数据过滤(过滤低质量数据) 测序得到的原始序列含有接头序列和低质量序列,为了保证信息分析的准确性,需要对原始数据进行质量控制,得到高质量序列(Clean Reads),原始序列…...

excel判断某一列(A列)中的数据是否在另一列(B列)中
如B列如果有7个元素,在A列右边的空白列中,输入如下公式: COUNTIF($B$1:$B$7,A1), 其中,$B$1:$B$7代表A列中的所有数据即绝对范围,A1代表B列中的一个单元格....

[环境配置]macOS上怎么查看vscode的commit id
macOS的commit id和windows上有点不一样,windows可以在帮助-关于查看 macOS则需要再左边第一个查看...

.net framework 3.5sp1组件安装进度条不动启动错误怎么解决
安装.NET Framework 3.5 SP1通常需要管理员权限。这是因为安装过程可能需要修改系统文件和注册表项,这些操作通常需要管理员权限才能执行。在Windows系统上,安装.NET Framework 3.5 SP1通常通过控制面板中的“启用或关闭Windows功能”选项进行࿰…...

学习threejs,利用THREE.ExtrudeGeometry拉伸几何体实现svg的拉伸
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.ExtrudeGeometry拉伸…...

大模型之三十二-语音合成TTS(coqui) 之二 fine-tune
在 大模型之三十-语音合成TTS(coqui)[shichaog CSDN]中提到了xttsv2的fine-tune。 数据情况: 我是从bilibili up主小Lin说提取了一些视频,然后进行了重新的fine-tune。 训练结果 如下图所示,上面波形幅度较大的是xttsv2原始模型的结果&am…...
JVM的内存模型是什么,每个区域的作用是什么,以及面试题(含答案)
JVM(Java 虚拟机)内存模型定义了 Java 程序在运行时如何分配、管理和优化内存。JVM 内存模型主要分为几个关键区域,每个区域有特定的作用: JVM 内存模型 堆内存(Heap): 作用:用于存…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...