记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来

开心一刻

一天我在切腊肠,儿子看了看,问我 儿子:爸,这腊肠过几了?过 6 了吧 我:啥叫过 6 了 儿子:这腊肠坏了吧 我憋了好一会,边笑边说:那叫过期,你记错数了吧

问题背景

项目中利用 DataX 实现离线同步,并实现了一些自定义的配置,其中 JVM_PARAM用来指定启动 DataX 时的 JVM 参数

shell 复制代码
java -server -Xms1g -Xmx1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=GBK -Ddatax.home=G:\\datax-tool\\datax -Dlogback.configurationFile=G:\\datax-tool\\datax\\conf\\logback.xml -classpath G:\\datax-tool\\datax\\lib\\* com.alibaba.datax.core.Engine -mode standalone -jobid -1 -job G:\\datax-tool\\datax\\job\\job.json

如上 java 命令后的

-Xms1g -Xmx1g -XX:+HeapDumpOnOutOfMemoryError

就是 JVM_PARAM 部分

简化来说:java 命令后的 JVM 参数可配置

项目是基于 JDK8 开发的, JDK8的默认垃圾回收器是 ParallelGC(Parallel Scavenge + Parallel Old),使用者可能觉得垃圾回收性能不行,就想换 G1 ,于是他配置 JVM_PARAM

-Xms1g -Xmx1g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC

启用 G1 垃圾回收器;结果这个离线同步任务,有时候成功,有时候失败,失败的异常信息如下

java 复制代码
Error: VM option 'UseG1GC' is experimental and must be enabled via -XX:+UnlockExperimentalVMOptions.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

问题排查

异常已经提示的很明显了:UseG1GC 是一个实验性参数,必须通过 -XX:+UnlockExperimentalVMOptions 来启用

使用者加上 -XX:+UnlockExperimentalVMOptions 参数后,任务多次执行也不报错了。我本以为事情到此就告一段落了,结果使用者来了一句:为什么有时候成功,有时候失败,你再查查,给我个结论

这么专业的提问,如果我不找到答案,反而显得我不够专业了

从任务的执行情况(有时候成功,有时候失败),再结合失败的异常信息,我得出一个结论

服务部署了多个节点,节点之间存在不同的 JDK 版本,有的版本支持 -XX:+UseG1GC,有的版本不支持单独的 -XX:+UseG1GC

然后我从注册中心确定了该服务一共 2 个节点,结合失败的执行记录(有记录服务器IP),成功锁定执行失败的节点,该节点的 JDK 版本如下

另一个节点的 JDK 版本如下

41 版本支持,292 版本反而不支持?为了保险起见,我写了个 Hello 类,然后在 292 上执行 java -XX:+UseG1GC Hello,结果正常输出,并没有异常

也就是说 292 版本也是支持的,难道不是用 292 版本的 JDK 启动的 DataX ?

正好服务器上有正在执行中的 DataX,我们先切到 root 用户(保证权限足够)

sudo -i

然后找到 datax 进程ID

最后确定用的是哪个 java(ls -l /proc/<pid>/exe

用的是 /home/dmdba/jdk1.8.0_371/bin/java,并不是 292 版本!!!

我们用 371 版本执行下 Hello

元凶不就找到了嘛

jdk1.8.0_371 不支持单独的 -XX:+UseG1GC

但是问题又来了:为什么 root 用户用的是 371 版本,而 yhuser 用的却是 292 版本?

这其实跟 Linux 的配置文件层级有关

层级 文件位置 生效范围 常见用途
系统全局 /etc/environment 所有用户、所有 Shell(包括图形界面) 设置全局性的基础路径,如 JAVA_HOME(该文件不支持变量引用,纯键值对)
系统全局(登录) /etc/profile/etc/profile.d/*.sh 所有用户登录 Shell 系统级别名、全局 umask,会遍历执行 /etc/profile.d/ 下的脚本
用户专属 ~/.bashrc (非登录) ~/.bash_profile~/.profile (登录) 仅限该用户 用户自定义别名、个人 PATH 追加(如 ~/my_bin

配置文件加载顺序

  1. /etc/environment
  2. /etc/profile
  3. /etc/profile.d/*.sh
  4. ~/.bash_profile 或 ~/.profile
  5. ~/.bashrc,

当同一变量在多个文件出现时,后读取的覆盖先读取的

回到我们的配置,/etc/profile.d/ 下配置的 292 版本(系统全局级别,root 与 yhuser 都能读取到

export PATH= JAVAHOME/bin: JAVA_HOME/bin: JAVAHOME/bin:PATH

root 用户下的 ~/.bash_profile 配置 371 版本(用户层级,root 能读取到,yhuser读取不到

export PATH= JAVAHOME/bin: JAVA_HOME/bin: JAVAHOME/bin:PATH

所以 yhuser 用的是 292 版本;而 root 用户,292 与 371 两个版本都能使用,但因为配置文件的先后加关系,以及 export PATH 的写法,使得 371 在 292 的前面

当执行 java 命令时,会按 $PATH 下的路径从左到右寻找 java 的可执行文件,一旦找到就使用,不会继续往后寻找

因为我们的服务是通过 root 部署的,所以服务通过 java 命令拉起 datax 时,用的自然也是 root 用户,自然而然就从 root 用户的 $PATH 中寻找 java 执行文件了,所以就找到了 371 版本

自此,前因后果是不是都清楚了

问题处理

找到问题了,就好处理了,方法有但不局限于以下几种

  1. -XX+UseG1GC -XX:+UnlockExperimentalVMOptions 一起配置,一起使用

  2. 去掉 root 用户级别的 java 配置

  3. 通过 yhuser 用户启动我们的服务

  4. 服务中加配置,指定 java 命令的绝对路径

    例如:/home/yhuser/app/java/jdk8u292-b10/bin/java -server -Xms1g -Xmx1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=GBK -Ddatax.home=G:\datax-tool\datax -Dlogback.configurationFile=G:\datax-tool\datax\conf\logback.xml -classpath G:\datax-tool\datax\lib\* com.alibaba.datax.core.Engine -mode standalone -jobid -1 -job G:\datax-tool\datax\job\job.json

额外补充

后面我确认了 jdk1.8.0_371 版本的 JDK 是 aarch64 架构的

至于 x86_64 架构的 jdk1.8.0_371 版本支不支持单独的 -XX+UseG1GC,你们去试试

总结

  1. java -version 只能查看当前用户使用的版本,并不代表整个服务器

  2. 查看服务到底使用的是哪个 jdk,可以根据进程ID找到java执行文件

    ls -l /proc//exe

  3. Linux 的配置文件有层级划分,也有优先级规则,当存在多版本时,注意版本间的优先级顺序

相关推荐
像我这样帅的人丶你还4 小时前
Java 后端详解(五):Redis 缓存
java·后端·全栈
plainGeekDev6 小时前
GreenDAO → Room
android·java·kotlin
亦暖筑序11 小时前
Java 8老系统AI Workflow实战:把一次性AI对话升级成可恢复工作流
java·后端
敲代码的彭于晏12 小时前
Bean 生命周期完全图解:前端同学也能看懂的 Spring 核心机制
java·前端·后端
plainGeekDev13 小时前
ButterKnife → ViewBinding
android·java·kotlin
像我这样帅的人丶你还1 天前
Java 后端详解(四):分页与搜索
java·javascript·后端
她的男孩1 天前
数据权限为什么不能只靠注解?Forge 的 Mapper 层 SQL 改写源码拆解
java·后端·架构
tntxia1 天前
Mybatis的日志输入
java
亦暖筑序1 天前
Java 8老系统Browser Agent实战:三层拦截把AI操作后台变成可审计流程
java·后端·设计模式