我们有一个使用Jetty WebSocketClient连接到服务器的工具。有时我们需要重新启动到服务器的连接。使用当前的代码,我们会在org.eclipse.jetty.util.thread.ShutdownThread上遇到内存泄漏,在那里保存所有使用过的org.eclipse.jetty.websocket.client.WebSocketClient实例。
与http://www.eclipse.org/jetty/documentation/current/jetty-websocket-client-api.html的文档一样,唯一要做的事情就是调用client.stop()。
代码或Jetty本身有什么问题吗?
下面是代码的“最小”示例。在其中,主运行一个“测试”,跟踪和比较所消耗的内存。
结果是:more heap used: 97 / 100 -告诉我有什么不对劲.
Task处理到服务器的连接(并保留其他信息):
package example;
import java.io.IOException;
import java.net.URI;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
class Task {
public enum StopReason {
CLOSED,
EXIT,
RESTART
}
private WebSocketClient client;
private ClientSocket socket;
private URI uri;
public Task(URI uri) {
this.uri = uri;
}
public void restart() throws IOException {
System.out.println("Task.restart");
stop(StopReason.RESTART);
synchronized (this) {
try {
this.wait(10);
} catch (InterruptedException e) {
// ignore
}
}
start();
}
public void start() throws IOException {
System.out.println("Task.start");
client = new WebSocketClient();
client.getPolicy().setIdleTimeout(120000);
client.setMaxIdleTimeout(1000);
socket = new ClientSocket(this);
try {
client.start();
System.out.println("Task started");
} catch (Exception e) {
throw new IOException("Failed to start WebSocketClient: " + e.getLocalizedMessage(), e);
}
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setRequestURI(uri);
client.connect(socket, request.getRequestURI(), request);
System.out.println("Task connected");
}
public void stop(Task.StopReason stopReason) throws IOException {
System.out.println("Task.stop " + stopReason);
if (stopReason != StopReason.CLOSED) {
try {
client.stop();
} catch (Exception e) {
throw new IOException("Task Error at 'stop': " + e.getLocalizedMessage(), e);
}
client.destroy();
try {
client.getConnectionManager().stop();
} catch (Exception e) {
System.err.println("Task Error at 'stop': " + e.getLocalizedMessage());
}
client.getConnectionManager().destroy();
}
System.out.println("Task.stop done " + stopReason);
}
}主类运行“测试”:
package example;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Map;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerEndpoint;
import org.glassfish.tyrus.server.Server;
import example.Task.StopReason;
@SuppressWarnings("javadoc")
@ServerEndpoint(value = "/test")
public class MemoryLeakDebug {
public static void main(String[] args) throws IOException, URISyntaxException, DeploymentException {
Map<String, Object> config = Collections.emptyMap();
Server server = new Server("localhost", 10000, "/test", config, DummyEndpoint.class);
server.start();
Task task = new Task(new URI("ws://localhost:10000/test/test"));
task.start();
long[] useds = new long[100];
try {
for (int i = 0; i < useds.length; i++) {
System.out.println("------------------------");
Runtime.getRuntime().gc();
long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("used: " + used);
useds[i] = used;
task.restart();
synchronized (task) {
try {
task.wait(100);
} catch (InterruptedException e) {
System.err.println(e.getLocalizedMessage());
}
}
}
} catch (AssertionError e) {
throw e;
} finally {
task.stop(StopReason.EXIT);
server.stop();
StringBuilder sb = new StringBuilder("useds:\n");
int index = 0;
int count = 0;
int moreCount = 0;
long last = -1;
for (long used : useds) {
sb.append((index++) + "\t" + used + "\n");
if (used > 0) {
count++;
}
if (last != -1) {
if (used > last) {
moreCount++;
}
}
last = used;
}
System.out.println(sb.toString());
System.out.println(moreCount + " / " + count);
if (moreCount > count * .75) {
throw new IllegalStateException("more heap used: " + moreCount + " / " + count);
}
}
}
}模拟端点:
package example;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@SuppressWarnings("javadoc")
@ServerEndpoint(value = "/test")
public class DummyEndpoint {
public DummyEndpoint() {
System.out.println("DummyEndpoint");
}
@OnClose
public void handleOnClose(Session session, CloseReason reason) {
System.out.println("DummyEndpoint@OnClose: " + reason + " " + session);
}
@OnError
public void handleOnError(Session session, Throwable error) {
System.out.println("DummyEndpoint@OnError: " + error + " " + session + " " + error.toString());
}
@OnMessage
public void handleOnMessage(Session session, String message) {
System.out.println("DummyEndpoint@OnMessage: " + message + " " + session);
}
@OnOpen
public void handleOnOpen(final Session session, EndpointConfig conf) {
System.out.println("DummyEndpoint@OnOpen: " + conf + " " + session);
}
}简化的虚拟客户端:
package example;
import java.io.IOException;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import example.Task.StopReason;
@SuppressWarnings("javadoc")
@WebSocket
public class ClientSocket {
private Session session;
private Task task;
public ClientSocket(Task task) {
this.task = task;
}
@OnWebSocketClose
public void handleClose(int statusCode, String reason) {
System.out.println("ClientSocket@OnWebSocketClose " + statusCode + " " + reason);
session = null;
try {
task.stop(StopReason.CLOSED);
} catch (IOException e) {
throw new IllegalStateException("Error at 'handleClose': " + e.getLocalizedMessage(), e);
}
}
@OnWebSocketConnect
public void handleConnect(Session session) {
System.out.println("ClientSocket@OnWebSocketConnect " + session);
this.session = session;
}
@OnWebSocketError
public void handleError(Throwable cause) {
System.err.println("ClientSocket@OnError" + cause);
cause.printStackTrace();
}
@OnWebSocketMessage
public void handleMessage(String message) {
System.out.println("ClientSocket@OnWebSocketMessage " + message);
}
public boolean isConnected() {
return session != null;
}
}我们的依赖关系:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>WebSocketClientDebug</artifactId>
<version>0.1.0-SNAPSHOT</version>
<properties>
<tyrus-version>[1.8.3,1.9)</tyrus-version>
</properties>
<dependencies>
<!-- + + + + + external dependencies + + + + + -->
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
<version>9.2.3.v20140905</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>[2.6.0,3.2.0)</version>
</dependency>
<!-- + + + + + external test dependencies + + + + + -->
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
<version>[1.8.3,1.9)</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly-server</artifactId>
<version>${tyrus-version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-server</artifactId>
<version>${tyrus-version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly-client</artifactId>
<version>${tyrus-version}</version>
</dependency>
</dependencies>
</project>发布于 2014-10-30 18:19:51
这是Jetty的WebSocketClient行为中的一个bug。
此修补程序适用于Jetty9.2.4(目前正在进行阶段测试,并将在今后7天内发布)
如果您想访问这个分阶段版本,请在irc.freenode.net/#jetty上与我联系,或者等待它发布。
https://stackoverflow.com/questions/26660366
复制相似问题