问题描述
请在下面的出现问题的应用程序/环境详细信息中查找。
- 在JRE版本1.8.0_231-b11上的Tomcat 9.0.35上部署的Java Web应用程序
- 该应用程序在Open shift Kubernetes分发平台上部署的docker容器中运行。
我看到应用程序中的许多线程有时进入阻塞状态几分钟。在线程转储分析中,发现java.net.InetAddress.getLocalHost调用花费太多时间。很多线程被卡在这里。将为应用程序中打印的每个记录器获取主机名。
问题是间歇性的。但是当发生这种情况时,应用程序/ tomcat将进入暂停状态,这导致大量线程的积累。一段时间(几秒钟)后,所有被阻塞的线程将同时被解除阻塞。由于请求的并发性,应用程序将用尽数据库连接,该数据库连接在池中维护,从而导致问题/缓慢/服务可用性。作为解决方法,我确保只将主机名访问一次到静态变量中,并在整个日志记录过程中使用该主机名。我想知道此问题的详细根本原因。
下面是线程转储中的示例:
"https-jsse-nio-8443-exec-13" #95 daemon prio=5 os_prio=0 tid=0x00007fccadbba800 nid=0xaf5 waiting for monitor entry 0x00007fcb912d1000
java.lang.Thread.State: BLOCKED (on object monitor)
at java.net.InetAddress.getLocalHost(InetAddress.java:1486)
- waiting to lock <0x00000005e71878a0> (a java.lang.Object)
解决方法
在JDK 8中,InetAddress.getLocalHost()
适用于as follows:
- 通过本地gethostname调用以字符串形式获取主机名。
- 如果自上次主机名解析以来不到5秒,则返回缓存的IP地址。
- 否则,解析主机名:
- 使用JDK内置的查找缓存,其默认TTL等于30秒;
- 使用系统调用来执行实际的DNS查找(取决于配置,该地址可能会进一步由OS和DNS服务器缓存)。
- 将解析的本地主机IP地址缓存5秒钟。
步骤2-4在全局cacheLock
下执行。如果在此过程中出现问题,则所有调用InetAddress.getLocalHost()
的线程都会在此锁处阻塞-正是您所观察到的。
通常,只要将主机地址硬编码在/etc/hosts
中,本地主机名解析就不会在网络呼叫中结束。但是在您的情况下,似乎涉及实际的网络请求(无论TTL何时终止)。当第一个DNS请求超时(毕竟UDP不是可靠的协议)时,就会发生延迟。
解决方案是将/etc/hosts
配置为包含本地主机的名称和地址,例如
192.168.1.23 myhost.mydomain
其中myhost.mydomain
与hostname
命令返回的字符串相同。
最后,如果在应用程序运行时不希望更改主机名,那么在应用程序级别上将其永久缓存一次似乎是一个很好的解决方法。
,要解决此问题,我仅加载一次主机名,并在应用程序启动期间将其缓存。我已经将此修复发布到生产环境中了,我们再也看不到线程阻塞问题了。
,也许服务器将使用 ipv6,如果未使用,您可以将 JVM 配置为仅使用 IPV4,为此将其添加到选项 -Djava.net.preferIPv4Stack=true 或如果只需要 ipv6 -Djava。 net.preferIPv6Stack=true。这将强制 JVM 使用正确的协议。