在 arthas cli 命令行,可使用 profiler 命令支持生成应用热点的火焰图,背后使用了 async-profiler 提供支持。在 Linux 环境下,async-profiler 默认只对 x86-64、aarch64 这两种操作系统架构提供了 glibc 版的动态链接库文件:
libasyncProfiler-linux-x64.so
libasyncProfiler-linux-arm64.so
但是在基于 musl libc 实现的 Alpine Linux 环境下,执行 profiler 命令会误加载到 libasyncProfiler-linux-x64.so 或者 libasyncProfiler-linux-arm64.so ,首先会提示缺少 libstdc++、libgcc 库文件,手工执行下面的命令安装后,再次执行 profiler 命令会造成应用 jvm 进程 crash : hs_err_pid17.log
Stack: [0x0000ffff60bf0000,0x0000ffff60dffac8], sp=0x0000ffff60dfe520, free space=2105k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C [libstdc++.so.6+0x1683dc]
C [libstdc++.so.6+0xd9b9c] std::locale::operator=(std::locale const&)+0x54
C [libstdc++.so.6+0xd9094] std::ios_base::_M_init()+0x4c
C [libstdc++.so.6+0x1153c0] std::basic_ios<char, std::char_traits<char> >::init(std::basic_streambuf<char, std::char_traits<char> >*)+0x18
C [ArthasJniLibrary5656461273075369895.tmp+0x273ac] Symbols::parseLibraries(NativeCodeCache**, int volatile&, int, bool)+0xf4
C [ArthasJniLibrary5656461273075369895.tmp+0x20208] VM::ready()+0x28
C [ArthasJniLibrary5656461273075369895.tmp+0x20c48] VM::init(JavaVM_*, bool)+0x430
C [ArthasJniLibrary5656461273075369895.tmp+0x210e0] JNI_OnLoad+0x14
C [libjava.so+0xecc4] Java_java_lang_ClassLoader_00024NativeLibrary_load+0xb4
j java.lang.ClassLoader$NativeLibrary.load(Ljava/lang/String;Z)V+0
j java.lang.ClassLoader.loadLibrary0(Ljava/lang/Class;Ljava/io/File;)Z+328
j java.lang.ClassLoader.loadLibrary(Ljava/lang/Class;Ljava/lang/String;Z)V+48
j java.lang.Runtime.load0(Ljava/lang/Class;Ljava/lang/String;)V+57
j java.lang.System.load(Ljava/lang/String;)V+7
j one.profiler.AsyncProfiler.getInstance(Ljava/lang/String;)Lone/profiler/AsyncProfiler;+23
j com.taobao.arthas.core.command.monitor200.ProfilerCommand.profilerInstance()Lone/profiler/AsyncProfiler;+165
j com.taobao.arthas.core.command.monitor200.ProfilerCommand.process(Lcom/taobao/arthas/core/shell/command/CommandProcess;)V+43
j com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(Lcom/taobao/arthas/core/shell/command/CommandProcess;)V+34
j com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(Lcom/taobao/arthas/core/shell/command/impl/AnnotatedCommandImpl;Lcom/taobao/arthas/core/shell/command/CommandProcess;)V+2
j com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(Lcom/taobao/arthas/core/shell/command/CommandProcess;)V+5
j com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(Ljava/lang/Object;)V+5
j com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run()V+11
..........................
.............
然后在 Alpine Linux 环境下查看基于 Alpine Linux 环境手工编译的 libasyncProfiler 和原始的基于 glibc + xxx Linux 环境编译的 libasyncProfiler,可以看到如下区别:
我提了一个 issue #741给 async-profiler ,async-profiler 的开发者建议我根据需要自行编译 libasyncProfiler
考虑到在 K8s 集群环境下,使用 Alpine Linux 作为 Java 应用基础镜像的用户比较多,所以这里考虑增加对 Alpine Linux x86-64 和 aarch64 这两种操作系统环境下使用 profiler 进行支持。
主要改动代码基于 async-profiler 源码和Alpine Linux x86-64 和 aarch64 这两种操作系统的 docker 镜像构建环境打包 libasyncProfiler so
FROM --platform=$TARGETPLATFORM base-registry.cn-hangzhou.cr.aliyuncs.com/cnstack/alpine-linux-gcc-builder-env:gcc-openjdk11
ARG TARGETOS
ARG TARGETARCH
ARG OSS_ENDPOINT
ARG ALIYUN_USER_AK
ARG ALIYUN_USER_SK
WORKDIR /root
RUN wget https://github.com/async-profiler/async-profiler/archive/refs/tags/v2.9.tar.gz -O async-profiler-2.9.tar.gz && \
tar zxvf async-profiler-2.9.tar.gz && cd async-profiler-2.9 && \
sed -i 's/CXXFLAGS=/CXXFLAGS=-static-libgcc -static-libstdc++ /' Makefile && make && \
curl https://gosspublic.alicdn.com/ossutil/install.sh | bash && \
ossutil -e ${OSS_ENDPOINT} -i ${ALIYUN_USER_AK} -k ${ALIYUN_USER_SK} \
cp build/libasyncProfiler.so oss://async-profiler/alpine/libasyncProfiler-${TARGETOS}-musl-${TARGETARCH}.so -u
Note: 这里默认将 libstdc++ 和 libgcc 作为静态依赖打进 libasyncProfiler so 文件,使客户在 Alpine Linux 环境下为了使用 arthas 的 profiler 命令时,不再需要单独安装 libstdc++ 和 libgcc.
生成的两个 libasyncProfiler so 文件: libasyncProfiler-linux-musl-amd64.so libasyncProfiler-linux-musl-arm64.so
libasyncProfiler so 文件加载入口处代码: core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java
if (OSUtils.isLinux()) {
if (OSUtils.isX86_64() && OSUtils.isMuslLibc()) {
profierSoPath = "async-profiler/libasyncProfiler-linux-musl-amd64.so";
} else if(OSUtils.isX86_64()){
profierSoPath = "async-profiler/libasyncProfiler-linux-x64.so";
} else if (OSUtils.isArm64() && OSUtils.isMuslLibc()) {
profierSoPath = "async-profiler/libasyncProfiler-linux-musl-arm64.so";
} else if (OSUtils.isArm64()) {
profierSoPath = "async-profiler/libasyncProfiler-linux-arm64.so";
}
}
测试情况