线程卡在 java.net.SocketInputStream.socketRead0 上

Java.net.SocketInputStream.socketRead0() API 的作用是什么?为什么其会频繁出现在多个线程转储文件中?为什么 fastThread.io 等线程转储文件分析工具会对其进行上报?这是否是我需要关心的信息?该问题的潜在解决方案是什么?下面就让我们来寻找一下这些问题的答案吧,

SocketInputStream.socketRead0() API 的作用是什么?

使用真实类比来了解新概念总是会比较容易。假设您正在给自己的妻子或女友打电话。电话接通后,如果她心情还不错的话,那您可能会立刻听到“亲爱的,怎么啦?”这样的话 :-)。如果接通后她正好有事(比如在上班、接小孩、健身之类的),那么她回复的时间可能会晚一些。假设电话接通时她正好有些情绪或是在生气,那您可能什么话都听不到。这就只有天知道了。可能几秒/几分钟后她会和您说话(当然也有可能直接把电话给你挂了)。因此,从接通电话到挂断电话,其中您的等待时间就基本上等同于 socketRead0() API 了(感谢 IBM 的 Douglas Spath 给出了这样漂亮的例子来解释 SocketRead0() API)。
应用程序可能需要通过各种协议(如:SOAP、REST、HTTP、HTTPS、JDBC、RMI…)来与多个远程应用程序建立接口,所有连接都要通过 JDK java.net 层来执行较为底层的 TCP-IP/Socket 操作。在该层中,SocketInputStream.socketRead0() API 用于读取和接收来自远程应用程序的数据。部分远程应用程序可能会立即响应,有些则可能需要一些时间才会享用,还有些则可能根本不会响应。在应用程序完全读取响应数据之前,其将会卡在此 java.net.SocketInputStream.socketRead0() API 上。

示例线程转储文件栈追踪信息

下面是展示线程卡在‘SocketInputStream.socketRead0’ API 上的栈追踪信息示例。无论协议如何,线程都卡在了 SocketInputStream.socketRead0() API 上。

"RMI TCP Connection(2)-192.xxx.xx.xx" daemon prio=6 tid=0x000000000a3e8800 nid=0x158e50 runnable [0x000000000adbe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(Unknown Source)
at java.net.SocketInputStream.read(Unknown Source)
at java.io.BufferedInputStream.fill(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
- locked (0x00000007ad784010) (a java.io.BufferedInputStream)
at java.io.FilterInputStream.read(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

图:RMI 线程卡在 SocketInputStream.socketRead0() API 上

"Thread-18" id=48 idx=0x9c tid=11696 prio=5 alive, in native, daemon
at jrockit/net/SocketNativeIO.readBytesPinned(Ljava/io/FileDescriptor;[BIII)I(Native Method)
at jrockit/net/SocketNativeIO.socketRead(SocketNativeIO.java:32)
at java/net/SocketInputStream.socketRead0(Ljava/io/FileDescriptor;[BIII)I(SocketInputStream.java)
at java/net/SocketInputStream.read(SocketInputStream.java:129)
at java/net/ManagedSocketInputStreamHighPerformanceNew.read(ManagedSocketInputStreamHighPerformanceNew.java:100)
at java/net/SocketInputStream.read(SocketInputStream.java:182)
at java/net/ManagedSocketInputStreamHighPerformanceNew.read(ManagedSocketInputStreamHighPerformanceNew.java:55)
at oracle/ons/InputBuffer.getNextString(InputBuffer.java:137)
at oracle/ons/ReceiverThread.run(ReceiverThread.java:295)
at jrockit/vm/RNI.c2java(JJJJJ)V(Native Method)

图:Oracle 数据库连接卡在 SocketInputStream.socketRead0() API

"AMQP Connection 192.xx.xxx.xxx:5672" prio=5 RUNNABLE
java.net.SocketInputStream.socketRead0(Native Method)
java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
java.net.SocketInputStream.read(SocketInputStream.java:170)
java.net.SocketInputStream.read(SocketInputStream.java:141)
java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
java.io.BufferedInputStream.read(BufferedInputStream.java:265)
java.io.DataInputStream.readUnsignedByte(DataInputStream.java:288)
com.rabbitmq.client.impl.Frame.readFrom(Frame.java:95)
com.rabbitmq.client.impl.SocketFrameHandler.readFrame(SocketFrameHandler.java:139)
com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:536)
java.lang.Thread.run(Thread.java:745)

图:RabbitMQ 卡在 SocketInputStream.socketRead0() API 上

"Thread-2012" id=218 idx=0x09c tid=196 prio=10 alive, in native, daemon
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:140)
at com.ibm.db2.jcc.t4.z.b(z.java:199)
at com.ibm.db2.jcc.t4.z.c(z.java:289)
at com.ibm.db2.jcc.t4.z.c(z.java:402)
at com.ibm.db2.jcc.t4.z.v(z.java:1170)
at com.ibm.db2.jcc.t4.cb.b(cb.java:40)
at com.ibm.db2.jcc.t4.q.a(q.java:32)
at com.ibm.db2.jcc.t4.sb.i(sb.java:135)
at com.ibm.db2.jcc.am.yn.gb(yn.java:2066)
at com.ibm.db2.jcc.am.zn.pc(zn.java:3446)
at com.ibm.db2.jcc.am.zn.b(zn.java:4236)
at com.ibm.db2.jcc.am.zn.fc(zn.java:2670)
at com.ibm.db2.jcc.am.zn.execute(zn.java:2654)
at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.execute(WSJdbcPreparedStatement.java:618)
at com.mycompany.myapp.MyClass.executeDatabaseQuery(MyClass.java:123)

图:IBM DB2 语句执行卡在 SocketInputStream.socketRead0() API 上

解决方案

如果有一个线程卡在 SocketInputStream.socketRead0 API 上,且在很长一段时间内均为从中恢复,那么最初发起此事务的客户将无法在其屏幕上看到任何响应。这可能会让用户感到迷惑。如果多个线程卡在 SocketInputStream.socketRead0 API 上且在很长一段时间内无法从中恢复的话,这可能会给程序可用性带来严重问题。

这里,我们将简要介绍几个针对该问题的潜在解决方案:

1. 工具超时设置

1.1.JVM 网络设置

1.2. setSoTimeout

1.3.JDBC

1.4.Oracle JDBC

1.5.Websphere

1.6.Axis2

2. 验证网络连接

3. 调试远程应用程序

4. 非阻塞 HTTP 客户端

# 1.工具超时设置

大部分应用程序不会设置从 SocketInputStream.socketRead0 中恢复的合适超时,因此其会在此 API 中卡住很长时间。设置合适的超时时所有程序都应该进行的绝佳自我防御机制。以下是一些您可以在程序中应用的超时设置:

1.1.JVM 网络设置

您可传递这两个功能强大的超时网络属性,其可全局式地用于使用 java.net.URLConnection 的所有协议处理程序:

  • -Dsun.net.client.defaultConnectTimeout
  • -Dsun.net.client.defaultReadTimeout

sun.net.client.defaultConnectTimeout 指明了与主机建立连接的超时(毫秒)。例如,对于 HTTP 连接而言,其是指与 HTTP 服务器建立连接时的超时。对于 FTP 连接而言,则是指与 HTTP 服务器建立连接时的超时。

sun.net.client.defaultReadTimeout 指定了在同资源建立了连接时从输入流中进行读取的超时(毫秒)。

有关 JVM 网络设置的更多详细信息可在此处查看。

1.2. setSoTimeout

如果您想直接使用 Sockets 进行编程,则可考虑通过调用 setSoTimeout() API 来设置 socket 的超时。

您可以毫秒向此 API 传递超时时间。如果远程应用程序没有在指定的超时时段内响应,则会抛出 java.net.SocketTimeoutException。此异常将释放线程,使其能够处理其他调用。注意:如果传递的超时值为 0,那么其将被解释为无限超时,即线程永远都不会超时。

1.3.JDBC

如果您在连接时使用的是 JDBC(Java 数据库连接),那么可考虑使用 setQueryTimeout() API

此 API 将设置 JDBC 驱动从数据库获取结果时所等待的秒数。如果超出限制,则会抛出 SQLTimeoutException。JDBC 驱动会在 execute()、executeQuery() 与 executeUpdate() 方法中应用该上限。默认情况下,运行语句的完成时间不会受限。

1.4.Oracle JDBC

如果您需要连接的是 Oracle 数据库,同时还观察到有许多线程卡在 SocketInputStream.socketRead0() API 上,则可考虑传递 -Doracle.jdbc.ReadTimeout 系统属性。

您需要在程序启动时传递上述参数。传递的值为毫秒。

1.5.Websphere

如果您的应用程序运行于 IBM Websphere,那么可考虑设置以下属性:

a.管理员可设置 webSphereDefaultQueryTimeout 数据源自定义属性。

b.第二个属性,syncQueryTimeoutWithTransactionTimeout,也可按照数据源自定义属性设置。设置后,WebSphere 将计算事务超时(如果运行于全局事务中)前的剩余时间,并且自动将查询超时设置为该值。

1.6.Axis2

您还可在 Web 服务客户端的 HTTP 传输策略集(HTTP Transport Policy Set)中设置“readTimeout”属性,或在应用程序代码中的 org.apache.axis2.context.MessageContext 处设置“timeout”。

# 2.验证网络连接

网络连接性或负载均衡器问题也可能会导致线程无法从 SocketInputStream.socketRead0 API 中恢复。之前我们也曾看到过,有时远程应用程序或许不会发出适当的 ACK 或 FIN 数据包。您可能必须让网络工程师或云托管供应商支持团队介入才能解决该问题。

在您这边可以使用 Wireshark 等 TCP/IP 跟踪工具来查看在与远程应用程序之间进行交换的数据包。这可以帮助您缩小范围,确定问题究竟存在于哪一边。

# 3.调试远程应用程序

有时,远程应用程序的性能问题可能会导致事务的处理速度变慢。在此类情形中,您需要充分意识到远程应用程序运行缓慢的问题,然后同对方共同进行修复。

# 4.非阻塞 HTTP 客户端

您还可考虑使用非阻塞 HTTP 客户端库,比如 GrizzlyNetty,其中没有会导致线程挂起的阻塞操作。不过此类解决方案更像是战略层面的解决方案,设计代码的变更和彻底的测试。

注意,虽然本文给出了不少解决方案,但除此之外也会有更多潜在解决方案。如果您有其他解决方案和超时设置方式,也想将其加入这篇博文的话,请在下方的反馈区联系我们。我们将很乐意听取您的意见,并进一步完善该博文。

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: