Java常量引用导致内存泄漏?

Java constant reference causes memory leak?

jvm jstat命令:

  jstat -gcutil 14378 2000 20
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  8.04   0.00  87.61   9.06  96.45  94.34    702   13.804     8    2.521   16.325

Eclipse内存分析定位到代码块:

public class MicrosoftConstant {

/**
 * TODO TTS 请求头设置
 */
public static final List<Header> TTS_REQUEST_HEADERS = new ArrayList<Header>(){
    {
        add(new BasicHeader("Content-Type", "application/ssml+xml"));
        add(new BasicHeader("X-Microsoft-OutputFormat", "xxx"));
        add(new BasicHeader("X-Search-AppId", "xxx"));
        add(new BasicHeader("X-Search-ClientID", "xxx"));
        add(new BasicHeader("User-Agent", "xxx"));
        add(new BasicHeader("Accept", "*/*"));
    }
};

}

使用代码块的常量:

List<Header> headers = MicrosoftConstant.TTS_REQUEST_HEADERS;
    headers.add(new BasicHeader("Ocp-Apim-Subscription-Key", microsoftConfig.getAppKey()));
    headers.add(new BasicHeader("Authorization", "Bearer " + authToken));
    InputStream audioStream = null;

        HttpEntity httpEntity = httpApiService.doPost(microsoftConfig.getTtsUrl(), body.getBytes(), headers);

接口在大量访问时不释放内存。

我不知道如何解决这个问题。谁能提供解决方案?

每次您 运行 第二个代码块时, TTS_REQUEST_HEADERS 都会在其中多获得两个项目。这意味着对于每个请求,还会添加两个 headers。 TTS_REQUEST_HEADERS object 的 数据与 final 不一致(只是指向 object 的指针是最终的,无法更改, object 本身可以改变,列表可以增长)并且 .add 方法会使它越来越长。

在为每个特定请求添加 who headers 之前,您可以 .clone TTS_REQUEST_HEADERS。这是一个浅拷贝,这意味着您最初在 TTS_REQUEST_HEADERS 中添加的 BasicHeaders 将是 re-used.

也许您必须将 public static final List<Header> 更改为 public static final ArrayList<Header> 才能使 .clone 正常工作。

您不仅有内存泄漏,还可能存在安全漏洞。问题是对于您执行的每个请求,您 添加 headers 到列表 TTS_REQUEST_HEADERS。这意味着随着每个请求,列表都会增长,而且永远不会缩小。

此外,您使用 'double brace initialization' anti-pattern,但在这种情况下这不是什么大问题。

更糟糕的是,这意味着实际请求可能会重复某些 headers 多次(这取决于 HTTP 客户端对重复 headers 所做的处理),这可能会无意中泄露有关先前请求的信息.

这个问题的解决方案是复制列表,将您的请求特定headers添加到副本,然后使用副本执行请求。为确保您不会无意中修改常量中的列表,请确保它不可修改(这样它实际上就是一个常量)。

为此,将列表定义为不可修改的列表,例如:

public static final List<Header> TTS_REQUEST_HEADERS = 
        Collections.unmodifiableList(Arrays.asList(
                new BasicHeader("Content-Type", "application/ssml+xml"),
                new BasicHeader("X-Microsoft-OutputFormat", "xxx"),
                new BasicHeader("X-Search-AppId", "xxx"),
                new BasicHeader("X-Search-ClientID", "xxx"),
                new BasicHeader("User-Agent", "xxx"),
                new BasicHeader("Accept", "*/*")));

或者,对于 Java 9 及更高版本,使用 List.of:

public static final List<Header> TTS_REQUEST_HEADERS = List.of(
        new BasicHeader("Content-Type", "application/ssml+xml"),
        new BasicHeader("X-Microsoft-OutputFormat", "xxx"),
        new BasicHeader("X-Search-AppId", "xxx"),
        new BasicHeader("X-Search-ClientID", "xxx"),
        new BasicHeader("User-Agent", "xxx"),
        new BasicHeader("Accept", "*/*"));

您的请求代码将变为:

// Copy the standard list of headers for this request
List<Header> headers = new ArrayList<>(MicrosoftConstant.TTS_REQUEST_HEADERS);
headers.add(new BasicHeader("Ocp-Apim-Subscription-Key", microsoftConfig.getAppKey()));
headers.add(new BasicHeader("Authorization", "Bearer " + authToken));
InputStream audioStream = null;

HttpEntity httpEntity = httpApiService.doPost(microsoftConfig.getTtsUrl(), body.getBytes(), headers);