首先在 JNI 的实际入口处 , 也就是 appendToClassLoaderSearch 的方法入口添加日志:
加了上面的日志后 , 发现问题更加令人头秃了:
没有报错的时候 , appendToClassLoaderSearch entry 会输出 。有报错的时候 , appendToClassLoaderSearch entry 反而没有输出 , 没执行到这儿? 这个和报错的日志对不上啊 , 难道是 stacktrace 信息骗了我们?
过了难熬的一晚上后 , 第二天请教了 dragonwell 的同学 , 大佬打日志的姿势是这样的:
tty-print_cr(\"internal error\"); 如果上面用不了 , 再用 printf(\"xxx\\");fflush(stdout); 这样加日志后 , 果然我们的日志都能打出来了 。
这是踩的第一个坑 , printf 要加上 fflush 才能保证输出成功 。
分析代码 后面又是不断加日志 , 最终发现 create_class_path_zip_entry 返回 NULL 。
找不到对应的 jar 文件?
继续排查 , 发现是 stat 报错 , 返回 No such file or directory 。 但是前面也提到了 , jarFile 的路径是存在的 , 难道 stat 不是线程安全的?
查了下文档[1
, 发现 stat 是线程安全的 。
于是又回过头来再看 , 这时候注意到 stat 的路径是不正常的:有的时候路径是空 , 有的时候路径是/home/admin/.opt/ArmsAgent/plugins/ahas-java-agent/ahas-java-agent.jarSHOT.jar , 从字符末尾可以看到 , 基本上是因为两个字符写到了同一片内存导致的;而且对应字符串长度也变成了一个不规律的数字了 。
那么问题就很明确了 , 开始查找这个字符串的生成 。 这个字符是 convertUft8ToPlatformString 生成的 。
字符编码转换有问题?
于是开始调试 utf8ToPlatform 的逻辑 , 这时候为了避免频繁加日志、重启容器 , 所以直接在 ECS 上运行 gdb 调试 jvm 。
结果发现 , 在 Linux 下 , utf8ToPlatform 就是直接 memcpy , 而且 memcpy 的目标地址是在栈上 。
这怎么看都不太可能有线程安全问题啊?
后来仔细查了下 , 发现和环境变量有关 , ECS 上编码相关的环境变量是 LANG=en_US.UTF-8 , 在容器上 centos:7 默认没有这个环境变量 , 此种情况下 , jvm 读到的是 ANSI_X3.4-1968 。
这儿是第二个坑 , 环境变量会影响本地编码转换 。
结合如上现象和代码 , 发现在容器环境下 , 还是要经过 iconv , 从 UTF-8 转到 ANSI_X3.4-1968 编码的 。
其实 , 这儿也可以推测出来 , 如果手动在容器中设置了 LANG=en_US.UTF-8 , 这个问题就不会再出现 。 额外的验证也证实了这点 。
然后又加日志 , 最终确认是 iconv 的时候 , 目标字符串写挂了 。
难道是 iconv 线程不安全?
iconv不是线程安全的!
查一下 iconv 的文档 , 发现它不是完全线程安全的:
通俗的说 , iconv 之前 , 需要先用 iconv_open 打开一个 iconv_t , 而且这个 iconv_t , 不支持多线程同时使用 。
至此 , 问题已经差不多定位清楚了 , 因为 jvm 把 iconv_t 写成了全局变量 , 这样在多个线程 append 的时候 , 就有可能同时调用 iconv , 导致竞态问题 。
这儿是第三个坑 , iconv 不是线程安全的 。
如何修复 先修复 one-java-agent
对于 Java 代码 , 非常容易修改 , 只需要加一个锁就可以了:
但是这儿有一个设计问题 , instrument 对象已经在代码中到处散落了 , 现在突然要加一个锁 , 几乎所有用到的地方都要改 , 代码改造成本比较大 。
于是最终还是通过 proxy 类来解决:
这样其他地方就只需要使用 InstrumentationWrapper 就可以了 , 也不会触发这个问题 。
jvm要不要修复
然后我们分析下 jvm 侧的代码 , 发现就是因为 iconv_t 不是线程安全的 , 导致 appendToClassLoaderSearch0 方法不是线程安全的 , 那能不能优雅的解决掉呢?
- 英特尔|Java:为什么 Java 是软件开发人员的首选?
- javascript|易烊千玺代言!华为Nova10和Nova10Pro相比,哪一款更值得入手?
- 抖音|java非静态内部类的使用
- Apple Watch|Java:为什么 Java 是软件开发人员的首选?
- 删除|你有踩坑吗?欠了超7000万的货款!淘宝五金冠店家接连暴雷!
- Java|广东推出全球首款穿戴式空调:3分钟温度降至16度
- javascript|Web前端:JavaScript有哪些主要特性?
- 智能手表|Java:Java中的多线程简介
- javascript|真我gtneo3、gt2大师探索版和红米k50之间咋选?
- Java|Java:Ruby on Rails与Java的比较