【发布时间】:2013-01-30 17:55:59
【问题描述】:
使用 spring 3.2.0.RELEASE resttemplate 和 httpcomponents 4.2.3 进行休息调用。内存占用量稳步增加,直到达到最大值。
以下是配置:
<bean id="myRestTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<constructor-arg index="0">
<bean factory-bean="httpClient" factory-method="get"/>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id="httpClient" class="com.mycompany.myproject.common.rest.HttpClient">
<constructor-arg index="0" ref="myKeyserverCA" ></constructor-arg>
<constructor-arg index="1" value="${com.mycompany.myproject.security.client.keyPassword}" ></constructor-arg>
<constructor-arg index="2" value="${default.max.total.connections}" ></constructor-arg>
<constructor-arg index="3" value="${default.max.host.connections}" ></constructor-arg>
</bean>
<bean id="myKeyserverCA"
class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:${com.mycompany.myproject.security.client.keyStore}" />
<property name="password" value="${com.mycompany.myproject.security.client.keyStorePass}" />
</bean>
HttpClient:
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.CoreConnectionPNames;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.security.KeyStore;
public class HttpClient {
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
private KeyStore keystore;
private String password;
private int MAX_TOTAL_CONNECTION;
private int MAX_PER_ROUTE;
public HttpClient(KeyStore keyStore, String keyPassword, int MAX_TOTAL_CONNECTION, int MAX_PER_ROUTE) {
this.keystore = keyStore;
this.password = keyPassword;
this.MAX_TOTAL_CONNECTION = MAX_TOTAL_CONNECTION;
this.MAX_PER_ROUTE = MAX_PER_ROUTE;
}
public org.apache.http.client.HttpClient get() {
PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(getSchemeRegistry(this.keystore, this.password));
connectionManager.setMaxTotal(MAX_TOTAL_CONNECTION);
connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE);
connectionManager.closeExpiredConnections();
org.apache.http.client.HttpClient httpClient = new DefaultHttpClient(connectionManager);
httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, DEFAULT_READ_TIMEOUT_MILLISECONDS);
return httpClient;
}
private static SchemeRegistry getSchemeRegistry(KeyStore keyStore, String keyPassword) {
SchemeRegistry registry = new SchemeRegistry();
try{
TrustManager[] trustManagerArray = { new TautologicalX509TrustManager() };
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyPassword.toCharArray());
SSLContext sslc = SSLContext.getInstance("TLS");
sslc.init(kmf.getKeyManagers(), trustManagerArray, null);
SSLSocketFactory sslSocketFactory = new SSLSocketFactory(sslc, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
registry.register(new Scheme("https", 443, sslSocketFactory));
return registry;
}catch(Exception e){
throw new RuntimeException(e.getMessage());
}
}
}
TautologicalX509TrustManager:
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class TautologicalX509TrustManager implements X509TrustManager {
private static final X509Certificate[] EMPTY_CERTIFICATES = new X509Certificate [0];
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return EMPTY_CERTIFICATES;
}
}
在这个组件上运行负载测试后,我们看到很多 SSLSocketImpl 对象和 byte[]。 对 SSLSocketImpl 的传入引用来自 Finalizer 对象。
当负载测试停止后我们在机器上执行 netstat 时,我们看不到任何与底层服务的开放 tcp 连接。但是在负载测试期间,TIME_WAIT状态的连接很多,ESTABLISHED状态的连接很少,但是在测试停止后它们都关闭了。
我们是否缺少任何用于关闭套接字的 API 调用?为什么我们的堆中有这么多 SSLSocketImpl 对象?
【问题讨论】:
-
如果我们只是对底层服务进行http调用而不是https,那么即使在高负载下也不会泄漏。
-
大约有 8000 个 com.sun.net.ssl.internal.ssl.SSLSocketImpl 对象,其中只有 50 个处于 connectionState 1 或 2。其余的处于 connectionState 7。 ConnectionStates: private静态最终 int cs_START = 0;私有静态最终 int cs_HANDSHAKE = 1;私有静态最终 int cs_DATA = 2;私有静态最终 int cs_RENEGOTIATE = 3;私有静态最终 int cs_ERROR = 4;私有静态最终 int cs_SENT_CLOSE = 5;私有静态最终 int cs_CLOSED = 6;私有静态最终 int cs_APP_CLOSED = 7;
-
(7) 一旦应用程序调用 close(),APP_CLOSED。然后它的行为就像一个封闭的套接字,例如 getInputStream() 抛出一个异常。 (2) 可以传输数据。 (1) HANDSHAKE 在允许流量之前选择会话参数。由于握手消息的排序要求,存在许多子状态。
-
如果我们运行 System.gc() [when getting heap-dump] 90% 的 SSLSocketImpl 对象被 gc'ed 并且内存占用量大幅下降,然后内存消耗再次恢复
-
好像不是内存泄漏。要确认,请以最低内存要求 (300MB) 运行您的测试,然后查看是否发生 OOM。在我看来,由于堆大小很大,GC 被延迟了。
标签: java spring garbage-collection