我正在编写一个端到端的测试,我的Java程序会释放它的所有资源 - 线程,服务器套接字,客户端套接字。它是一个库,因此通过退出JVM释放资源不是一种选择。 Testing the releasing of threads很简单,因为你可以向ThreadGroup询问其中的所有线程,但我还没有找到一个很好的方法来获取当前JVM正在使用的所有网络套接字的列表。 / p>
有没有办法从JVM获取所有客户端和服务器套接字的列表,类似于netstat?我正在使用Netty OIO(即{ Java 7上的{3}}和java.net.ServerSocket)。解决方案需要在Windows和Linux上运行。
我的第一个偏好是使用纯Java从JVM中询问它。我试图寻找一个MX Bean或类似的,但没有找到任何。
另一种选择可能是连接到JVM的分析/调试API并询问Socket和ServerSocket的所有实例,但我不知道如何做到这一点以及是否可以在没有本机代码的情况下完成(AFAIK,{{ 3}}仅限本机)。此外,它不应该使测试变慢(即使我最慢的端到端测试只有0.5秒,其中包括启动另一个JVM进程)。
如果询问JVM不起作用,第三个选项是创建一个设计,在设置时跟踪所有套接字。这样做的缺点是可能会遗漏一些创建套接字的地方。由于我正在使用Netty,它似乎可以通过包装java.net.Socket并使用JVMTI来实现。
答案 0 :(得分:16)
我能够挂钩java.net.Socket
和java.net.ServerSocket
以及间谍这些类的所有新实例。可以看到完整的代码in the source repository。以下是该方法的概述:
当实例化Socket或ServerSocket时,其构造函数中的第一件事是对setImpl()
的调用,它实例化实际实现套接字功能的对象。默认实现是java.net.SocksSocketImpl
的实例,但可以通过设置自定义java.net.SocketImplFactory
到java.net.Socket#setSocketImplFactory
和java.net.ServerSocket#setSocketFactory
来覆盖它。
java.net.SocketImpl
的所有实现都是包含私有的,这有点复杂,但有一些反思并不太难:
private static SocketImpl newSocketImpl() {
try {
Class<?> defaultSocketImpl = Class.forName("java.net.SocksSocketImpl");
Constructor<?> constructor = defaultSocketImpl.getDeclaredConstructor();
constructor.setAccessible(true);
return (SocketImpl) constructor.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
在创建时监视所有套接字的SocketImplFactory实现看起来像这样:
final List<SocketImpl> allSockets = Collections.synchronizedList(new ArrayList<SocketImpl>());
ServerSocket.setSocketFactory(new SocketImplFactory() {
public SocketImpl createSocketImpl() {
SocketImpl socket = newSocketImpl();
allSockets.add(socket);
return socket;
}
});
请注意,setSocketFactory / setSocketImplFactory只能被调用一次,因此您需要只有一个测试(就像我拥有它),或者您必须创建一个静态单例(yuck!)来保存该间谍。 / p>
那么问题是如何确定套接字是否已关闭? Socket和ServerSocket都有一个方法isClosed()
,但它使用这些类的布尔内部来跟踪它是否被关闭 - SocketImpl实例没有一种简单的方法来检查它是否被关闭。 (顺便说一下,Socket和ServerSocket都有SocketImpl支持 - 没有“ServerSocketImpl”。)
值得庆幸的是,SocketImpl引用了它支持的Socket或ServerSocket。上述setImpl()
方法会调用impl.setSocket(this)
或impl.setServerSocket(this)
,并且可以通过调用java.net.SocketImpl#getSocket
或java.net.SocketImpl#getServerSocket
来获取该引用。
这些方法再次是包私有的,因此需要进行一些反思:
private static Socket getSocket(SocketImpl impl) {
try {
Method getSocket = SocketImpl.class.getDeclaredMethod("getSocket");
getSocket.setAccessible(true);
return (Socket) getSocket.invoke(impl);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static ServerSocket getServerSocket(SocketImpl impl) {
try {
Method getServerSocket = SocketImpl.class.getDeclaredMethod("getServerSocket");
getServerSocket.setAccessible(true);
return (ServerSocket) getServerSocket.invoke(impl);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
请注意,可能不会在SocketImplFactory中调用getSocket / getServerSocket,因为Socket / ServerSocket仅在从那里返回SocketImpl之后才设置它们。
现在有了检查我们的测试所需的所有基础结构,无论我们想要什么关于Socket / ServerSocket:
for (SocketImpl impl : allSockets) {
assertIsClosed(getSocket(impl));
}
完整的源代码是here。
答案 1 :(得分:5)
我自己没有尝试过,但JavaS专家通讯也提出了类似的问题:
http://www.javaspecialists.eu/archive/Issue169.html
在底部,他描述了使用AspectJ的方法。您可以将切入点放在创建套接字的构造函数周围,并且具有在那里注册套接字创建的代码。