一行 JVM 参数解决 HttpClient 卡死:强制 Java 禁用 IPv6

发布于 更新于 687 字 7 分钟阅读

#一行 JVM 参数解决 HttpClient 卡死:强制 Java 禁用 IPv6

#问题场景

某天线上服务突然出现偶发性超时,排查了半天,日志里卡在 HttpClient.execute() 一动不动,没有任何异常抛出,线程就那么"假死"了。重启能恢复,但过一阵子又复现,让人头皮发麻。

最终定位到根因:Java 在网络解析时优先尝试了 IPv6,而服务器环境实际只有 IPv4 通,导致连接超时前的等待时间被无限拉长。

报错:

SQL
dMiniProgramBackend-debug  | java.net.http.HttpConnectTimeoutException: HTTP connect timed out
WordMiniProgramBackend-debug  |         at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:917)
WordMiniProgramBackend-debug  |         at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
WordMiniProgramBackend-debug  |         at com.xkcoding.http.support.java11.HttpClientImpl.exec(HttpClientImpl.java:67)
WordMiniProgramBackend-debug  |         at com.xkcoding.http.support.java11.HttpClientImpl.get(HttpClientImpl.java:140)
WordMiniProgramBackend-debug  |         at com.xkcoding.http.HttpUtil.get(HttpUtil.java:131)
WordMiniProgramBackend-debug  |         at me.zhyd.oauth.utils.HttpUtils.get(HttpUtils.java:37)
WordMiniProgramBackend-debug  |         at me.zhyd.oauth.request.AuthWechatMiniProgramRequest.getAccessToken(AuthWechatMiniProgramRequest.java:37)
WordMiniProgramBackend-debug  |         at me.zhyd.oauth.request.AuthDefaultRequest.login(AuthDefaultRequest.java:61)
WordMiniProgramBackend-debug  |         at com.word.user.service.impl.UserServiceImpl.login(UserServiceImpl.java:54)
WordMiniProgramBackend-debug  |         at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
WordMiniProgramBackend-debug  |         at java.base/java.lang.reflect.Method.invoke(Method.java:565)
WordMiniProgramBackend-debug  |         at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
WordMiniProgramBackend-debug  |         at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:715)
WordMiniProgramBackend-debug  |         at com.word.user.service.impl.UserServiceImpl

NOTEPUB_MATH_0

0.login(<generated>)
WordMiniProgramBackend-debug  |         at com.word.user.controller.UserController.login(UserController.java:48)
WordMiniProgramBackend-debug  |         at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
WordMiniProgramBackend-debug  |         at java.base/java.lang.reflect.Method.invoke(Method.java:565)
WordMiniProgramBackend-debug  |         at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:252)
WordMiniProgramBackend-debug  |         at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:184)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:934)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:853)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:86)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:866)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1000)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:903)
WordMiniProgramBackend-debug  |         at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:649)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:874)
WordMiniProgramBackend-debug  |         at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:710)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:128)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:142)
WordMiniProgramBackend-debug  |         at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:355)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:272)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:110)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:199)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:165)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:77)
WordMiniProgramBackend-debug  |         at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:492)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:113)
WordMiniProgramBackend-debug  |         at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:83)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:72)
WordMiniProgramBackend-debug  |         at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)
WordMiniProgramBackend-debug  |         at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
WordMiniProgramBackend-debug  |         at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
WordMiniProgramBackend-debug  |         at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1779)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:946)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:480)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:57)
WordMiniProgramBackend-debug  |         at java.base/java.lang.Thread.run(Thread.java:1474)
WordMiniProgramBackend-debug  | Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
WordMiniProgramBackend-debug  |         at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68)
WordMiniProgramBackend-debug  |         at java.net.http/jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1751)
WordMiniProgramBackend-debug  |         at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:1348)
WordMiniProgramBackend-debug  | Caused by: java.net.ConnectException: HTTP connect timed out
WordMiniProgramBackend-debug  |         at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69)
WordMiniProgramBackend-debug  |         ... 2 more
WordMiniProgramBackend-debug  | 2026-06-07 07:32:31 http-nio-8080-exec-1 me.zhyd.oauth.log.Log(error:45) [ERROR] - Failed to login with oauth authorization.
WordMiniProgramBackend-debug  | me.zhyd.oauth.exception.AuthException: HTTP connect timed out
WordMiniProgramBackend-debug  |         at me.zhyd.oauth.utils.HttpUtils.check(HttpUtils.java:123)
WordMiniProgramBackend-debug  |         at me.zhyd.oauth.utils.HttpUtils.getBody(HttpUtils.java:129)
WordMiniProgramBackend-debug  |         at me.zhyd.oauth.request.AuthWechatMiniProgramRequest.getAccessToken(AuthWechatMiniProgramRequest.java:37)
WordMiniProgramBackend-debug  |         at me.zhyd.oauth.request.AuthDefaultRequest.login(AuthDefaultRequest.java:61)
WordMiniProgramBackend-debug  |         at com.word.user.service.impl.UserServiceImpl.login(UserServiceImpl.java:54)
WordMiniProgramBackend-debug  |         at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
WordMiniProgramBackend-debug  |         at java.base/java.lang.reflect.Method.invoke(Method.java:565)
WordMiniProgramBackend-debug  |         at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
WordMiniProgramBackend-debug  |         at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:715)
WordMiniProgramBackend-debug  |         at com.word.user.service.impl.UserServiceImpl

NOTEPUB_MATH_1

0.login(<generated>)
WordMiniProgramBackend-debug  |         at com.word.user.controller.UserController.login(UserController.java:48)
WordMiniProgramBackend-debug  |         at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
WordMiniProgramBackend-debug  |         at java.base/java.lang.reflect.Method.invoke(Method.java:565)
WordMiniProgramBackend-debug  |         at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:252)
WordMiniProgramBackend-debug  |         at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:184)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:934)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:853)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:86)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:866)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1000)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:903)
WordMiniProgramBackend-debug  |         at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:649)
WordMiniProgramBackend-debug  |         at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:874)
WordMiniProgramBackend-debug  |         at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:710)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:128)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:142)
WordMiniProgramBackend-debug  |         at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:355)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:272)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:110)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:199)
WordMiniProgramBackend-debug  |         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:107)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:165)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:77)
WordMiniProgramBackend-debug  |         at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:492)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:113)
WordMiniProgramBackend-debug  |         at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:83)
WordMiniProgramBackend-debug  |         at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:72)
WordMiniProgramBackend-debug  |         at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)
WordMiniProgramBackend-debug  |         at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
WordMiniProgramBackend-debug  |         at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
WordMiniProgramBackend-debug  |         at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1779)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:946)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:480)
WordMiniProgramBackend-debug  |         at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:57)
WordMiniProgramBackend-debug  |         at java.base/java.lang.Thread.run(Thread.java:1474)

#根因分析

Java 的网络栈默认是双栈(Dual-Stack) 模式,即 IPv4 和 IPv6 同时启用。当发起一个 HTTP 请求时:

  1. DNS 解析返回域名对应的所有 IP 地址(可能同时包含 A 记录和 AAAA 记录)
  2. Java 默认会优先尝试 IPv6(如果系统支持)
  3. 如果 IPv6 不通(比如服务器没配 IPv6、防火墙拦截了),Java 会傻傻等到超时
  4. 超时后再 fallback 到 IPv4——但此时上层业务可能已经超时了

这在容器化环境(Docker / K8s)中尤其常见,因为容器网络默认就启用了 IPv6 支持。


#解决方法

一行参数搞定,在启动命令中加入:

Shell
java -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv4Addresses=true -jar your-app.jar
参数 作用
-Djava.net.preferIPv4Stack=true 强制 JVM 使用 IPv4 协议栈,彻底禁用 IPv6
-Djava.net.preferIPv4Addresses=true 当 DNS 同时返回 IPv4/IPv6 地址时,优先使用 IPv4

两个参数一起上,双重保险。


#Docker 环境配置

如果是 Docker 部署,在 docker-compose.yml 或 Dockerfile 中设置:

YAML
# docker-compose.yml
services:
  your-service:
    image: your-image
    environment:
      - JAVA_TOOL_OPTIONS=-Djava.net.preferIPv4Stack=true -Djava.net.preferIPv4Addresses=true

为什么用 JAVA_TOOL_OPTIONS而不是 JAVA_OPTS

  • JAVA_TOOL_OPTIONS​ 是 JVM 原生读取的环境变量,无需应用做任何适配,任何 Java 进程启动时都会自动加载
  • JAVA_OPTS​ 需要你的启动脚本(如 entrypoint.sh)手动拼接,容易遗漏

K8s 同理:

YAML
# deployment.yaml
spec:
  containers:
    - name: your-app
      image: your-image
      env:
        - name: JAVA_TOOL_OPTIONS
          value: "-Djava.net.preferIPv4Stack=true -Djava.net.preferIPv4Addresses=true"

#验证是否生效

启动后检查日志,如果看到类似输出说明已生效:

Text
Picked up JAVA_TOOL_OPTIONS: -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv4Addresses=true

也可以用 jinfo 验证运行中的进程:

Shell
jinfo -sysprops <pid> | grep preferIPv4

输出:

Text
java.net.preferIPv4Stack = true
java.net.preferIPv4Addresses = true

#延伸思考

这其实反映了一个更普遍的教训:默认行为不一定适合你的环境。 Java 的双栈设计在 IPv6 普及的今天看似"先进",但在很多内网、容器网络中反而是坑。类似的还有:

  • -Djava.awt.headless=true:服务器环境没 GUI,不设这个图片处理可能报错
  • -Dfile.encoding=UTF-8:避免跨平台文件编码乱码
  • -Duser.timezone=Asia/Shanghai:统一时区,避免日志时间混乱

建议把这些基础 JVM 参数做成团队的基础镜像或启动脚本模板,一劳永逸。


[!Tip] 一句话总结 JAVA_TOOL_OPTIONS="-Djava.net.preferIPv4Stack=true -Djava.net.preferIPv4Addresses=true"— 加这行到环境变量,HttpClient 假死问题一去不返。

zxb的博客

评论

还没有评论,来说点什么吧。

评论经发布者审核后公开
46 篇文档

文档树

7 个章节

本文目录

搜索文档

输入关键词,立即搜索当前分享。