是什么原因导致套接字连接在完整 GC 后运行缓慢?

我们有一个客户端服务器应用程序,1个服务器,大约10个客户端。它们使用自定义查询通过 tcp 套接字进行通信。

系统已经运行了好几个月,但在某些时候,在每天计划服务器FULL GC大约需要50s之后,我们发现客户端发送的查询与从服务器收到的响应之间的时间很长,>10-20s。大约3个小时后,系统恢复,一切又运行正常。

在调查问题时,我们发现:

  1. 客户端和服务器上都没有垃圾回收问题
  2. 服务器上的查询处理时间很短。
  3. 服务器上的负载很高。
  4. 网络带宽未饱和。
  5. 在 FULL GC 期间未重置连接(在此之前,每日 FULL GC 是正常事件)
  6. 机器和操作系统最近从 Centos 6(内核 2.6.32)更改为 Centos 7(内核 3.10.0),但新配置经过了广泛的测试。此外,Oracle JDK版本从1.7.65更改为1.7.75。

我们在服务器上进行了线程转储:

java.lang.Thread.State: RUNNABLE
    at java.io.FilterInputStream.read(FilterInputStream.java:83)
    at util.network.BytesBasedSocketConnection$ReadConnectionRunnable.run(BytesBasedSocketConnection.java:293)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)

具体如下:FilterInputStream.read()

    public int read() throws IOException {
    return in.read();
}

在我们的代码中是一个 .inBufferedInputStream

问题是:为什么大多数连接在完整GC暂停后变慢?为什么堆栈跟踪以 FilterInputStream.read() 结尾?它不应该在 BufferedInputStream 或套接字输入流中的某个位置结束吗?此读取是否会导致服务器上的高负载?

我们用于读取的代码:

int constructLength = _socketDIS.readInt();
ByteArrayOutputStream constructBOAS = new ByteArrayOutputStream(constructLength);
for (int i = 0; i != constructLength; i++)
      constructBOAS.write(_socketDIS.read());
constructBOAS.close();
byte[] bytes = constructBOAS.toByteArray();

哪里:

_socketDIS = new DataInputStream(new BufferedInputStream(_socket.getInputStream()));

下面是来自正常工作的客户端连接的堆栈跟踪:

java.lang.Thread.State: RUNNABLE
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(SocketInputStream.java:152)
    at java.net.SocketInputStream.read(SocketInputStream.java:122)
    at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:254)
    - locked <0x00007f522cbebca8> (a java.io.BufferedInputStream)
    at java.io.DataInputStream.readInt(DataInputStream.java:387)
    at util.network.BytesBasedSocketConnection$ReadConnectionRunnable.run(BytesBasedSocketConnection.java:287)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)

更新:

关于EJP的答案:

  1. 没有涉及EOS,连接已启动,但它们非常慢

  2. 即使有EOS,我也看不出代码如何在EOS上旋转,这是受值限制的。但是,建议的改进仍然有效。forconstructLength

  3. 有问题的堆栈跟踪以对 () 完成的读取结束,该读取是从 继承自 ,请参阅上面的代码。,则不 缺少 .这里有一个调用,这个有自己的定义方法。但是堆栈跟踪停在中间,没有到达。为什么?DataInputStream(_socketDIS.read()FilterInputStream.read()DataInputStreamBufferedInputStreamread()FilterInputStream.read()in.read()BufferedInputStreamread()BufferedInputStream.read()


答案 1

一次读取一个字节会浪费 CPU。扔掉这个:

int constructLength = _socketDIS.readInt();
ByteArrayOutputStream constructBOAS = new ByteArrayOutputStream(constructLength);
for (int i = 0; i != constructLength; i++)
      constructBOAS.write(_socketDIS.read());
constructBOAS.close();
byte[] bytes = constructBOAS.toByteArray();

并使用以下:

int constructLength = _socketDIS.readInt();
byte[] bytes = new byte[constructLength];
_socketDIS.readFully(bytes);

NB显然不是一个,而是一个无缓冲的。_socketDISBufferedInputStreamDataInputStream,

编辑

为什么堆栈跟踪以 FilterInputStream.read() 结尾?

仔细观察。 不实现所有三个 read() 重载。其中一个,我忘记了哪个,是在FilterInputStream中实现的,基类,另外两个重载调用它。BufferedInputStream

它不应该在缓冲输入流中的某个地方结束吗?

不,见上文。

还是在套接字输入流中?

是的,如果它被阻塞了,但它不是,可能是因为你在流结束时旋转,因为你的代码很差。

此读取是否会导致服务器上的高负载?

是的。


答案 2

堆栈跟踪显示您正在使用 .我建议你研究时间表。延迟很可能仅仅是因为阅读是按某种时间表进行的 - 这在我看来是愚蠢的。ScheduledThreadPoolExecutor


推荐