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);
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);