小说网站开发背景中铁建设集团招聘700人
news/
2025/9/27 2:06:44/
文章来源:
小说网站开发背景,中铁建设集团招聘700人,数据网站建设工具模板,定制网站开发介绍图从 Java Agent 报错开始#xff0c;到 JVM 原理#xff0c;到 glibc 线程安全#xff0c;再到 pthread tls#xff0c;逐步探究 Java Agent 诡异报错。
背景
由于阿里云多个产品都提供了 Java Agent 给用户使用#xff0c;在多个 Java Agent 一起使用的场景下#xff0…从 Java Agent 报错开始到 JVM 原理到 glibc 线程安全再到 pthread tls逐步探究 Java Agent 诡异报错。
背景
由于阿里云多个产品都提供了 Java Agent 给用户使用在多个 Java Agent 一起使用的场景下造成了总体 Java Agent 耗时增加各个 Agent 各自存储导致内存占用、资源消耗增加。
MSE 发起了 one-java-agent 项目能够协同各个 Java Agent同时也支持更加高效、方便的字节码注入。
其中各个 Java Agent 作为 one-java-agent 的 plugin在 premain 阶段是通过多线程启动的方式来加载从而将启动速度由 O(n)降低到 O(1)降低了整体 Java Agent 整体的加载时间。
问题
但最近在新版 Agent 验证过程中one-java-agent 的 premain 阶段发现有如下报错
2022-06-15 06:22:47 [oneagent plugin arms-agent start] ERROR c.a.o.plugin.PluginManagerImpl -start plugin error, name: arms-agent
com.alibaba.oneagent.plugin.PluginException: start error, agent jar::/home/admin/.opt/ArmsAgent/plugins/ArmsAgent/arms-bootstrap-1.7.0-SNAPSHOT.jarat com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:113)at com.alibaba.oneagent.plugin.PluginManagerImpl.startOnePlugin(PluginManagerImpl.java:294)at com.alibaba.oneagent.plugin.PluginManagerImpl.access$200(PluginManagerImpl.java:22)at com.alibaba.oneagent.plugin.PluginManagerImpl$2.run(PluginManagerImpl.java:325)at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.InternalError: nullat sun.instrument.InstrumentationImpl.appendToClassLoaderSearch0(Native Method)at sun.instrument.InstrumentationImpl.appendToSystemClassLoaderSearch(InstrumentationImpl.java:200)at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:100)... 4 common frames omitted
2022-06-16 09:51:09 [oneagent plugin ahas-java-agent start] ERROR c.a.o.plugin.PluginManagerImpl -start plugin error, name: ahas-java-agent
com.alibaba.oneagent.plugin.PluginException: start error, agent jar::/home/admin/.opt/ArmsAgent/plugins/ahas-java-agent/ahas-java-agent.jarat com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:113)at com.alibaba.oneagent.plugin.PluginManagerImpl.startOnePlugin(PluginManagerImpl.java:294)at com.alibaba.oneagent.plugin.PluginManagerImpl.access$200(PluginManagerImpl.java:22)at com.alibaba.oneagent.plugin.PluginManagerImpl$2.run(PluginManagerImpl.java:325)at java.lang.Thread.run(Thread.java:855)
Caused by: java.lang.IllegalArgumentException: nullat sun.instrument.InstrumentationImpl.appendToClassLoaderSearch0(Native Method)at sun.instrument.InstrumentationImpl.appendToSystemClassLoaderSearch(InstrumentationImpl.java:200)at com.alibaba.oneagent.plugin.TraditionalPlugin.start(TraditionalPlugin.java:100)... 4 common frames omitted
熟悉 Java Agent 的同学可能能注意到这是调用 Instrumentation.appendToSystemClassLoaderSearch 报错了。
但首先 appendToSystemClassLoaderSearch 的路径是存在的其次这个报错的真实原因是在 C部分比较难排查。
但不管怎样还是要深究下为什么出现这个错误。
首先我们梳理下具体的调用流程下面的分析都是基于此来分析的
- Instrumentation.appendToSystemClassLoaderSearch (java)- appendToClassLoaderSearch0 (JNI)- appendToClassLoaderSearch|- AddToSystemClassLoaderSearch| -create_class_path_zip_entry| -stat-convertUft8ToPlatformString- iconv
打日志、确定现场
因为这个问题在容器环境下有 10% 的概率出现比较容易复现于是就用 dragonwell8 的最新代码加日志确认下现场。
首先在 JNI 的实际入口处也就是 appendToClassLoaderSearch 的方法入口添加日志 加了上面的日志后发现问题更加令人头秃了
没有报错的时候appendToClassLoaderSearch entry 会输出。有报错的时候appendToClassLoaderSearch entry 反而没有输出没执行到这儿
这个和报错的日志对不上啊难道是 stacktrace 信息骗了我们
过了难熬的一晚上后第二天请教了 dragonwell 的同学大佬打日志的姿势是这样的
tty-print_cr(internal error);如果上面用不了再用 printf(xxx\n);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 上编码相关的环境变量是 LANGen_US.UTF-8在容器上 centos:7 默认没有这个环境变量此种情况下jvm 读到的是 ANSI_X3.4-1968。
这儿是第二个坑环境变量会影响本地编码转换。
结合如上现象和代码发现在容器环境下还是要经过 iconv从 UTF-8 转到 ANSI_X3.4-1968 编码的。
其实这儿也可以推测出来如果手动在容器中设置了 LANGen_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 程序直接用 ThreadLoal 来存储 iconv_t 就能解决了。
但是 cpp 这边虽然 C 11 支持 thread_local但首先 jdk8 还没用 C 11这个可以参考 JEP 其次C 11 的也仅仅支持 thread_local 的 set 和 getthread_local 的初始化、销毁等生命周期管理还不支持比如没办法在线程结束时自动回收 iconv_t 资源。
那咱们就 fallback 到 pthread因为 pthread 提供了 thread-specific data可以做类似的事情。
pthread_key_create 创建 thread-local storage 区域pthread_setspecific 用于将值放入 thread-local storagepthread_getspecific 用于从 thread-local storage 取出值最重要的pthread_once 满足了 pthread_key_t 只能初始化一次的需求。另外也需要提到的pthread_once 的第二个参数就是线程结束时的回调我们就可以用它来关闭 iconv_t避免资源泄漏。
总之 pthread 提供了 thread_local 的全生命周期管理。于是最终代码如下用 make_key 初始化 thread-local storage 于是编译 JDK 之后打镜像、批量重启数次 pod就没有再出现文章开头提到的问题了。
总结
在整个过程中从 Java 到 JNI/JVMTi再到 glibc再到 pthread踩了很多坑
printf 要加上 fflush 才能保证输出成功环境变量会影响本地字符编码转换iconv 不是线程安全的使用 pthread thread-local storage 来实现线程局部变量的全生命周期管理
从这个案例中沿着调用栈、代码逐步还原问题、并提出解决方案希望大家能对 Java/JVM 多了解一点。
参考链接
[1] 文档
https://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
[2] one-java-agent 修复的链接
https://github.com/alibaba/one-java-agent/issues/31
[3] dragonwell 修复的链接
https://github.com/alibaba/dragonwell8/pull/346
[4] one-java-agent 给大家带来了更加方便、无侵入的微服务治理方式
https://www.aliyun.com/product/aliware/mse
原文链接
本文为阿里云原创内容未经允许不得转载。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/919008.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!