发送HTTP时的OutputStream OutOfMemoryError

我试图将一个大的video/图像文件从本地文件系统发布到一个http路径,但一段时间后我遇到内存不足错误…

这是代码

public boolean publishFile(URI publishTo, String localPath) throws Exception { InputStream istream = null; OutputStream ostream = null; boolean isPublishSuccess = false; URL url = makeURL(publishTo.getHost(), this.port, publishTo.getPath()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); if (conn != null) { try { conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestMethod("PUT"); istream = new FileInputStream(localPath); ostream = conn.getOutputStream(); int n; byte[] buf = new byte[4096]; while ((n = istream.read(buf, 0, buf.length)) > 0) { ostream.write(buf, 0, n); //<--- ERROR happens on this line.......??? } int rc = conn.getResponseCode(); if (rc == 201) { isPublishSuccess = true; } } catch (Exception ex) { log.error(ex); } finally { if (ostream != null) { ostream.close(); } if (istream != null) { istream.close(); } } } return isPublishSuccess; } 

HEre是我得到的错误……

 Exception in thread "Thread-8773" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2786) at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:94) at sun.net.www.http.PosterOutputStream.write(PosterOutputStream.java:61) at com.test.HTTPClient.publishFile(HTTPClient.java:110) at com.test.HttpFileTransport.put(HttpFileTransport.java:97) 

HttpUrlConnection正在缓冲数据,以便它可以设置Content-Length标头(根据HTTP规范 )。

如果目标服务器支持,则另一种方法是使用“ 分块 ”传输。 这将一次仅缓冲一小部分数据。 但是,并非所有服务都支持它(例如,Amazon S3不支持)。

另一个替代方案(并且更好的方法)是使用Jakarta HttpClient 。 您可以在文件的请求中设置“实体”,连接代码将适当地设置请求标头。


编辑: nos评论说OP可以调用HttpURLConnection.setFixedLengthStreamingMode(long length) 。 我没有意识到这种方法; 它是在1.5中添加的,从那以后我没有使用过这个类。

但是,我仍然建议使用Jakarta HttpClient,原因很简单,它减少了OP必须维护的代码量。 代码是样板,但仍有可能出错:

  • OP正确处理在输入和输出之间复制的循环。 通常当我看到这样的示例时,海报要么没有正确检查返回的缓冲区大小,要么继续重新分配缓冲区。 恭喜,但你现在必须确保你的继任者尽可能多的关心。
  • exception处理并不是那么好。 是的,OP记得在finally区块中关闭连接,并再次祝贺。 除了任何一个close()调用都可以抛出IOException ,保持另一个不执行。 并且该方法作为一个整体抛出Exception ,因此编译器不会帮助捕获类似的错误。
  • 我计算了31行代码来设置和执行响应(不包括响应代码检查和URL计算,但包括try / catch / finally)。 使用HttpClient,这将在半打LOC的范围内。

即使OP已经完美地编写了这段代码,并将其重构为类似于Jakarta Commons IO中的方法, 他/她也不应该这样做 。 此代码已由其他人编写和测试。 我知道重写它是浪费我的时间,并怀疑这也浪费了OP的时间。

 conn.setFixedLengthStreamingMode((int) new File(localpath).length()); 

对于缓冲,您可以将流覆盖到BufferedOutputStreamBufferedInputStream中

你可以在那里找到分块上传的好例子: gdata-java-client

问题是HttpURLConnection类使用字节数组来存储数据。 据推测,你正在推动的video占用的内存比现有的多。 你有几个选择:

  1. 增加应用程序的内存。 您可以使用-Xmx1024m选项为您的应用程序提供1GB内存。 这将增加您可以存储在内存中的数据量。

  2. 如果仍然内存不足,您可能需要考虑尝试使用另一个库来推送不会将数据全部存储在内存中的video。 Apache Commons HttpClient具有这样的function。 有关更多信息,请访问此站点: http : //hc.apache.org/httpclient-3.x/features.html 。 有关大型文件的多部分表单上载,请参阅本节: http : //hc.apache.org/httpclient-3.x/methods/multipartpost.html

除了基本的GET操作之外的任何东西,内置的java.net HTTP内容都不是很好。 建议使用Apache Commons HttpClient 。 它可以让你做更直观的事情:

 PutMethod put = new PutMethod(url); put.setRequestEntity(new FileRequestEntity(localFile, contentType)); int responseCode = put.executeMethod(); 

它取代了很多锅炉板代码。

HttpsURLConnection#setChunkedStreamingMode(1024 * 1024 * 10); // 10MB chunk这可确保任何文件(任何大小)都通过https连接进行流式传输,无需内部缓冲。 当文件大小或内容长度未知时,应使用此选项。

您的问题是,当N> 1时,您正尝试将Xvideo字节修复为X / N字节的RAM。

您需要将video读入较小的缓冲区并随时将其写出,或者将文件缩小或增加进程可用的内存。

检查堆大小。 如果您采用默认值,则可以使用-Xmx来增加它。