首页 > 解决方案 > 为什么 RestTemplate 会消耗过多的内存?

问题描述

问题

为什么 Spring 的 RestTemplateG1 Old Generation在发送文件时会使用过多的堆(尤其是 )。

语境

我们观察到RestTemplatePOST在通过请求发送文件时会消耗过多的内存。我们使用 Spring 的WebClient作为比较,它的行为完全正常。

我们在 github 上创建了一个包含完整代码的演示项目。重要的部分是以下片段:

private void sendFileAsOctetStream(File file) {
    final RequestEntity<FileSystemResource> request = RequestEntity.post(URI.create("http://localhost:8080/file"))
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(new FileSystemResource(file));
    restTemplate.exchange(request, void.class);
}

private void sendFileAsOctetStream(File file) {
    webClient.post()
            .uri("/file")
            .body(BodyInserters.fromResource(new FileSystemResource(file)))
            .exchange()
            .block();
}

jconsole我们观察了使用两种实现发送一个 550MB 文件时的内存使用情况(左边是WebClient,右边是RestTemplateWebClient消耗几兆字节,而RestTemplate需要 2.7 千兆字节:

在此处输入图像描述

  1. 用于清理老年代的初始手动 GC
  2. 请求
  3. 手动 GC(仅适用于RestTemplate

标签: javaspringspring-mvcresttemplate

解决方案


这是由于默认情况下RestTemplate简单地使用未配置SimpleClientHttpRequestFactory来创建请求。

上面提到的请求工厂有一个bufferRequestBody默认设置为的标志true,这会导致在发送大请求时内存消耗非常高。

来自的javadoc SimpleClientHttpRequestFactory#setBufferRequestBody()

指示此请求工厂是否应在内部缓冲请求正文。默认为真。通过 POST 或 PUT 发送大量数据时,建议将此属性更改为 false,以免内存不足。这将导致 ClientHttpRequest 直接流式传输到底层 HttpURLConnection(如果事先知道 Content-Length),或者将使用“分块传输编码”(如果事先不知道 Content-Length)。

您可以在使用其他重载构造函数之一创建时提供自己的请求工厂,RestTemplate并将提到的标志设置false为请求工厂:

@Bean
public RestTemplate restTemplate() {
    SimpleClientHttpRequestFactory rf = new SimpleClientHttpRequestFactory();
    rf.setBufferRequestBody(false);
    return new RestTemplate(rf);
}

推荐阅读