[鸿蒙PC命令行移植适配] 移植 jemalloc 到鸿蒙PC的完整实践

欢迎加入【开源鸿蒙PC社区】,一起共建鸿蒙化C/C++三方库生态。

前言

jemalloc 是工业级通用内存分配器,主打低碎片、高并发可扩展,广泛用于数据库、缓存、Web 服务等高性能服务端程序。本文将介绍如何将 jemalloc 适配到鸿蒙PC平台,提供可复用的静态库、头文件、pkg-config 及验证程序,并支持 HNP 打包。

前置条件

环境/工具 描述
适配库 jemalloc
开源协议 BSD-2-Clause
源码版本 5.3.1
目标平台 鸿蒙PC
操作系统平台 macOS
原仓库地址 https://github.com/jemalloc/jemalloc
鸿蒙化适配仓库地址 https://atomgit.com/OpenHarmonyPCDeveloper/ohos-jemalloc
鸿蒙应用集成jemalloc三方库源代码仓库地址 https://atomgit.com/unisources/JemallocDemo
Ubuntu中搭建鸿蒙PC 三方库交叉编译构建开发环境 https://blog.csdn.net/zl392321162/article/details/159284760
macOS 中搭建鸿蒙 PC 三方库交叉编译开发环境 https://blog.csdn.net/zl392321162/article/details/159284830
Windows 10 上安装和使用 WSL 2、安装 Ubuntu 24 详细指南 https://blog.csdn.net/yyz_1987/article/details/148545443
鸿蒙 PC 命令行适配指南(Mac 版) https://blog.csdn.net/qq_39132095/article/details/154796658
鸿蒙 PC 生态三方软件移植:开发环境搭建及三方库移植指南 https://blog.csdn.net/yyz_1987/article/details/154794871
OpenHarmony Linux 命令行工具适配实战:基于 Cursor × WSLtree 2.2.1 交叉编译与 HNP 打包全流程指南 https://weishuo.blog.csdn.net/article/details/155140843
社区维护的鸿蒙 PC 生态命令行工具构建框架 lycium_plusplus https://atomgit.com/OpenHarmonyPCDeveloper/lycium_plusplus
鸿蒙PC端二进制文件签名命令行使用指南 https://blog.csdn.net/jianguo888888/article/details/156644386
hnp包验证环境 https://bxming.blog.csdn.net/article/details/155073889

系列索引

篇章 标题 内容
第一篇 概述与环境配置 Lycium 概念、构建机要求、OHOS SDK 配置
第二篇 项目结构与适配目录创建 目录结构、community vs thirdparty、创建适配目录
第三篇 HPKBUILD 编写详解 元数据字段、过程函数、三种构建系统写法
第四篇 构建执行与产物获取 构建流程、日志分析、多库递归、HAP 集成
第五篇 流程图与角色职责 完整流程图、各角色职责、协作时序
第六篇 关键注意事项与最佳实践 依赖管理、架构超集、日志调试、外部适配仓
第七篇 快速参考与模板 入门步骤、模板、完整案例、检查清单

一、环境配置

1 OpenHarmony SDK 安装

1.1 下载 SDK(环境搭建)
  1. 在浏览器中打开 DCP 每日构建列表https://dcp.openharmony.cn/workbench/cicd/dailybuild/dailylist
  2. 在列表中按本机操作系统选择对应产物(名称随版本变化,以页面为准):
开发机系统 选择产物(关键词)
Windows / Linux ohos-sdk-full(OHOS 全量 SDK,用于交叉编译)
macOS mac-sdk-full(Mac 版 SDK 包)

下载到本机后解压(请将下面文件名替换为你实际下载的包名):

cd ~
# Linux / macOS 示例(包名以 DCP 页面为准)
tar -zvxf <你下载的-sdk-xxx>.tar.gz

Windows 请使用资源管理器或 7-Zip 等工具解压对应 .zip / .tar.gz 包。

说明:每日构建会更新版本与文件名,不要固定使用旧文档中的直链;以 DCP 页面上当前可下载的 SDK 包为准。解压后若顶层目录名不是 ohos-sdk,可将该目录移动或软链为 ~/ohos-sdk(或 Windows 下放到固定路径),与下文 OHOS-SDK 配置一致。

再进入 ohos-sdk 根目录解压(文件名以 darwin 目录下为准,下例版本号仅作演示):

cd ~/ohos-sdk   # Linux / macOS;Windows 请先 cd 到 OHOS_SDK 目录
unzip native-darwin-arm64-6.0.0.46-Beta1.zip
unzip toolchains-darwin-arm64-6.0.0.46-Beta1.zip

Windows 可对两个 zip 右键解压到当前文件夹,或使用 tar/Expand-Archive 等工具解压到 ohos-sdk 根目录。

解压完成后,应得到 native/toolchains/ 等目录(含 llvmsysroothnpcli 等),再配置 2.1.3 中的环境变量。

1.2 SDK 目录结构
ohos-sdk/
├── native/
│   ├── llvm/bin/          # 编译器工具链
│   ├── sysroot/           # 系统根目录(头文件和库)
│   └── build-tools/       # 构建工具
└── toolchains/
    └── hnpcli            # HNP打包工具
1.3 环境变量配置

编辑 ~/.zshrc(如果使用 zsh)或 ~/.bash_profile(如果使用 bash):

# OpenHarmony SDK 路径
export OHOS_SDK=~/ohos-sdk

# 添加到 PATH
export PATH="$OHOS_SDK/native/llvm/bin:$PATH"
export PATH="$OHOS_SDK/native/build-tools/cmake/bin:$PATH"
export PATH="$OHOS_SDK/toolchains/bin:$PATH"

# 验证
source ~/.zshrc  # 或 source ~/.bash_profile
1.4 验证 SDK 配置
# 检查 SDK 路径
echo $OHOS_SDK

# 检查工具是否在 PATH 中
which clang
which cmake
which hnpcli

# 检查 SDK 工具目录
ls $OHOS_SDK/native/llvm/bin/
ls $OHOS_SDK/native/build-tools/cmake/bin/
ls $OHOS_SDK/toolchains/

# 验证工具版本
clang --version
cmake --version
hnpcli --version

二、适配步骤

1. 分析jemalloc构建特性

jemalloc 是高性能服务端内存管理的事实标准,以低碎片、高并发、可观测三大核心能力,支撑无数大规模分布式系统。

核心优势:

  • 极致低碎片:多级尺寸分级、arena 隔离、thread cache,长稳服务内存占用平滑GitHub。
  • 高并发性能
    • 每个线程私有 tcache,无锁快速分配 / 释放。
    • 多核多 arena 锁拆分,大幅降低竞争。
  • 可观测与调优:内置**统计、监控、堆分析(jeprof)、内存泄漏排查。
  • 跨平台:Linux、FreeBSD、macOS、Windows(MSVC)、Android 均支持。
  • 生产验证:被 Redis、MongoDB、MySQL、Firefox、Netty、Rust(早期) 等采用。

2. 创建适配项目

参考前置条件列表完成lycium_plusplus交叉框架编译环境搭建,以及lycium_plusplus交叉框架代码克隆,在lycium_plusplus/thirdparty创建目标库jemalloc适配目录为ohos_jemalloc

为什么是ohos_jemalloc?为了和源库名称做区分,表示该库用于ohos设备。

ohos_jemalloc创建可以借助编辑器工具(如VSCode)或者使用文件夹在lycium_plusplus/thirdparty目录下创建目标库适配目录ohos_jemalloc,也可以执行以下命令进行创建。

# 我将交叉编译框架克隆在根目录,此处可改为正确的目录地址
cd ~/lycium_plusplus/thirdparty
mkdir ohos_jemalloc

3. 编写HPKBUILD

然后将lycium/template/HPKBUILD拷贝到thirdparty/ohos_jemalloc目录下,HPKBUILDlycium交叉编译框架完成编译构建的核心配置文件,定义包的元信息、依赖、构建和打包逻辑。需要根据模板在HPKBUILD开头声明jemalloc的基本信息,这些字段被lycium用于下载、组织和记录。jemalloc上游构建方式是Autotools(autoconf + configure + make),应为 configure(或手写 build()autotools),因此buildtools=configure

# lycium_plusplus/thirdparty/ohos-jemalloc/HPKBUILD
pkgname=jemalloc # 库名
pkgver=5.3.1 # 库版本
pkgrel=0 # 发布号
pkgdesc="General-purpose scalable concurrent malloc implementation" # 库描述
url="https://github.com/jemalloc/jemalloc" # 官网链接
archs=("arm64-v8a") # cpu 架构
license=("BSD-2-Clause")
depends=() # 依赖库的目录名 必须保证被依赖的库的archs是当前库的archs的超集
makedepends=("autoconf" "automake" "libtool" "pkg-config") # 构建库时的依赖工具->需要用户安装的工具
source="https://github.com/jemalloc/jemalloc/archive/refs/tags/${pkgver}.tar.gz" # 库源码下载链接

downloadpackage=true # 是否自动下载压缩包,如若不写默认 true. (应对一些特殊情况,代码只能 git clone (项目中依赖 submoudle ))
autounpack=true # 是否自动解压,如若不写默认 true, 如果为 false 则需要用户在 prepare 函数中自行解压
buildtools=configure # 编译方法, 暂时支持cmake, configure, make等, 是什么就填写什么. 如若不写默认为cmake.

builddir=jemalloc-${pkgver} # 源码压缩包解压后目录名 编译目录名
packagename=${builddir}.tar.gz # 压缩包名
字段 配置值 用途
pkgname jemalloc pkgname=jemalloc:包名,用于在 LYCIUM_ROOT/usr/ 下创建安装目录、标识依赖关系。
pkgver 6.9.10 pkgver=5.3.1:上游版本号,与 https://github.com/jemalloc/jemalloc 仓库最新发行版本保持一致。
pkgrel 0 pkgrel=0:包发布号,当同一上游版本需要重新打包时递增,首次适配为 0。
pkgdesc - 包的简短描述,取自 jemalloc 官方 README
url - 上游项目主页 URL
archs ("arm64-v8a") archs=("arm64-v8a"):声明支持的架构数组。此处仅列出 arm64-v8a,但代码内部实际也处理了 armeabi-v7ax86_64,此处是声明"主要支持"而非"仅支持"。当前鸿蒙 PC 设备为 arm64 架构,因此必须配置arm64-v8a
license ("BSD-2-Clause") jemalloc 采用 BSD-2-Clause协议。
depends () 无其他依赖库。
makedepends () 无编译时依赖。
source refs/tags/${pkgver}.tar.gz 源码包下载地址。使用 ${pkgver} 变量拼接,下载 jemalloc 5.3.1release tarball
downloadpackage true downloadpackage=true:告诉 lycium 构建系统自动下载 source 指定的源码包。如果设置为false需要在目标库目录下手动下载源码包。
autounpack true autounpack=true:下载后自动解压,无需手动解压步骤。如果设置为false需要手动解压。
buildtools cargo buildtools=make:声明构建工具类型。
builddir ${pkgname}-${pkgver} builddir=${pkgname}-${pkgver}:解压后的源码目录名。prepare()/build()/package() 均通过 cd $builddir 进入此目录。
packagename ${builddir}.tar.gz packagename=${builddir}.tar.gz:源码包文件名,用于校验/定位。

buildtools=configure,因此需要根据系列索引-HPKBUILD编写详情-4.2 configure(autotools)项目修改preparebuildpackage函数。

# 为编译设置环境,如设置环境变量,创建编译目录等
prepare() {
    cd $builddir
    # 需要先生成 configure 脚本
    if [ ! -f configure ]; then
        ./autogen.sh
    fi
    cd ${OLDPWD}
}

# ${OHOS_SDK} oh sdk安装路径
# $ARCH 编译的架构是 archs 的遍历
# $LYCIUM_ROOT/usr/$pkgname/$ARCH 安装到顶层目录的usr/$pkgname/$ARCH
# 执行编译构建的命令
build() {
    # 如果是cmake构建 "$@"=-DCMAKE_FIND_ROOT_PATH="..." -DCMAKE_TOOLCHAIN_FILE="..." -DCMAKE_INSTALL_PREFIX="..." 依赖库的搜索路径,toolchain file 路径,安装路径
    cd $builddir
    . "${LYCIUM_ROOT}/script/envset.sh"
    # 设置编译主机架构
    case "$ARCH" in
        arm64-v8a)   setarm64ENV;  jemalloc_host=aarch64-linux-ohos ;;
        armeabi-v7a) setarm32ENV;  jemalloc_host=arm-linux-ohos ;;
        x86_64)      setx86_64ENV; jemalloc_host=x86_64-linux-ohos ;;
        *) echo "Unsupported ARCH=$ARCH"; return 1 ;;
    esac
    # setarm64ENV 会把 CPP 设为 CXX(clang++),jemalloc configure 需要 C 预处理器
    export CPP="${CC} -E"
    # "$@" 含 configuredependpath 注入的 --prefix=$LYCIUM_ROOT/usr/jemalloc/$ARCH/
    # --with-lg-page=16:与 fd/bat 等在 aarch64(含鸿蒙)上 jemalloc 页大小约定一致
    ./configure "$@" \
        --host="${jemalloc_host}" \
        --enable-shared \
        --with-lg-page=16 \
        CC="${CC}" \
        CXX="${CXX}" \
        AR="${AR}" \
        RANLIB="${RANLIB}" \
        > $buildlog 2>&1
    $MAKE >> $buildlog 2>&1
    # 对最关键一步的退出码进行判断
    ret=$?
    cd $OLDPWD
    return $ret
}

# 打包安装
package() {
    cd $builddir
    $MAKE install >> $buildlog 2>&1
    cd $OLDPWD
}

4 HNP 打包配置

lycium_plusplus/thirdparty/ohos-jemalloc目录下创建hnp.json文件,因为在HPKBUILD中存在archive函数,用于将产物打包为 output/<arch>/<pkgname>_<ver>.tar.gz,或执行 HNP 打包,详细介绍可参考系列索引-构建执行与产物获取

{
    "type": "hnp-config",
    "name": "jemalloc",
    "version": "5.3.1",
    "install": {}
}

5 第一次交叉编译

HPKBUILD中函数不做改变,在lycium_plusplus/lycium目录下打开终端工具,输入./build.sh ohos-jemalloc进行第一次交叉编译。

问题一:cat: include/jemalloc/jemalloc_defs.h: No such file or directory

原因cat: include/jemalloc/jemalloc_defs.h: No such file or directory 不是 jemalloc 源码本身缺文件,而是 configure 阶段本应生成的头文件没有真正落盘,随后 jemalloc.sh 在拼装 jemalloc.h 时用 cat 读取该文件失败。

解决方法:在 HPKBUILD 的 build() 里、./configure 之前设置export PATH="/usr/bin:/bin:/opt/homebrew/bin:$PATH" PATH(构建脚本层面,不改 jemalloc 源码)。在运行 ./configure 构建前,让系统 diff 优先于 SDK 工具链。然后重新执行交叉编译命令./build.sh ohos-jemalloc

export PATH="/usr/bin:/bin:/opt/homebrew/bin:$PATH"
问题二:exception specification in declaration does not match previous declaration void JEMALLOC_SYS_NOTHROW *je_malloc(size_t size)

原因:host=aarch64-linux-ohos 在 jemalloc configure 里匹配 *-*-linux*,按 Linux/glibc 处理,启用了 JEMALLOC_USE_CXX_THROW。生成的 jemalloc.h 中,je_malloc 等通过宏展开为带 throw() / nothrow 的声明。编译 jemalloc_cpp.cpp 时会包含 OHOS sysroot 的 stdlib.h(musl),其中上述函数 无 C++ 异常说明。C++ 要求重声明的异常说明一致,因此报错;纯 C 的 .c 文件不受影响。

解决方法:在 HPKBUILD 的 build() 里,在 configure 时关闭 C++ 集成:

./configure "$@" \
    --host="${jemalloc_host}" \
    --enable-shared \
    --with-lg-page=16 \
    CC="${CC}" \
    --disable-cxx \
    AR="${AR}" \
    RANLIB="${RANLIB}" \
    > $buildlog 2>&1

image-20260517173640588

如果提示ALL JOBS DONE!!!表示当前交叉编译没有问题,编译后的产物,可以在lycium/usr/目录和out/arm64-v8目录下查看。

image-20260517173931695

三、 使用AtomCode快速完成鸿蒙应用集成jemalloc

jemalloc是一个三方库,并没有生成可执行的二进制文件,对于三方库,我们可以通过将三方库集成到鸿蒙应用中来验证,接下来使用AtomCode来完成在鸿蒙应用中集成jemalloc三方库,可以参考手把手教你在鸿蒙应用中集成鸿蒙化abseil-cpp三方库,Demo已开源博文创建鸿蒙应用程序,并将交叉编译后的lycium/usr/jemalloc拷贝到/JemallocDemo/entry/src/main/cpp/thirdparty目录下。然后在JemallocDemo目录下打开终端工具,执行atomcode启动工具,并输入集成指令(当然这个指令不是固定的,可以按照自己的喜欢来写)已经完成jemalloc三方库交叉编译,现在需要在当前应用中集成该三方库,并完成对应的功能验证,交叉编译产物已经放在entry/src/main/cpp/thirdparty目录下。先梳理jemalloc提供的能力,然后完成编码。

image-20260517180930871

image-20260517193313769

使用AtomCode完成示例项目代码编写后,在DevEco Studio工具中为项目进行签名,然后将鸿蒙PC与开发机连接,使用DevEco Studio安装运用应用,项目源代码JemallocDemo

image-20260517194236653

四、 FAQ

Q1: jemalloc.h 为什么不能在 C++ 环境中直接包含?

背景: 直接在 napi_init.cpp#include <jemalloc.h> 会导致编译错误,错误类似:

error: 'valloc' was not declared in this scope
     void *valloc(size_t size);
          ^~~~~~
In file included from .../jemalloc.h:36:
note: 'valloc' conflicts with declaration 'void* valloc(size_t)'
     JEMALLOC_OVERRIDE_VALLOC
     ^~~~~~~~~~~~~~~~~~~~~~~~

根因:

jemalloc 5.3.1 为支持内存分配器替换(override),在 jemalloc.h 内部通过宏定义了一系列符号别名:

// jemalloc.h 第33-36行
#define JEMALLOC_OVERRIDE_MEMALIGN
#define JEMALLOC_OVERRIDE_VALLOC

这些宏展开后,je_memalign 被定义为 memalignje_valloc 被定义为 valloc。同时,jemalloc.h 内部用这些宏名声明函数:

JEMALLOC_EXPORT void *JEMALLOC_NOTHROW je_memalign(size_t alignment, size_t size);
// 展开为:
JEMALLOC_EXPORT void *JEMALLOC_NOTHROW memalign(size_t alignment, size_t size);

在 C 环境下,memalign/valloc 是 POSIX 扩展函数,声明没问题。但在 C++11+ 环境下,这些函数属于非标准扩展,<cstdlib><stdlib.h> 可能不提供它们的声明,或者声明带有不同的异常规范(throw() vs noexcept 不匹配),导致冲突。

解决方案: 不包含 jemalloc.h,改为手动 extern "C" 声明所需的函数:

extern "C" {
    void *malloc(size_t size);
    void *calloc(size_t num, size_t size);
    void *realloc(void *ptr, size_t size);
    void free(void *ptr);
    size_t malloc_usable_size(void *ptr);
    void malloc_stats_print(void (*write_cb)(void *, const char *), void *cbopaque, const char *opts);
    int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen);
    void *mallocx(size_t size, int flags);
    void dallocx(void *ptr, int flags);
}

关键前提: 此方案成立的前提是 jemalloc .so 导出的符号是无前缀的(malloc 而非 je_malloc)。如果编译 jemalloc 时使用了 --with-jemalloc-prefix=je_,则库导出 je_malloc 等符号,此时可以直接包含 jemalloc.h 并定义 JEMALLOC_NO_DEMANGLE

参考条件对应的配置: 编译 jemalloc 时使用默认 prefix(je_),配置为:

./configure --host=aarch64-linux-android --prefix=$(pwd)/arm64-v8a \
            CC=<交叉编译器>

此时生成的 .so 导出的是 je_mallocje_free 等符号,可以通过包含 jemalloc.h 正常使用。

Q2: OHOS_ARCH 变量从哪里来,需要手动设置吗?

背景: CMakeLists.txt 中使用 ${OHOS_ARCH} 定位 jemalloc 交叉编译产物:

set(JEMALLOC_DIR ${NATIVERENDER_ROOT_PATH}/thirdparty/jemalloc/${OHOS_ARCH})

解答: OHOS_ARCH 由 OpenHarmony 构建系统(hvigor + CMake)自动注入,编译时根据目标设备架构自动设置为 arm64-v8aarmeabi-v7ax86_64。开发者不需要也不应在 CMakeLists.txt 中手动设置此变量。

如果本地调试时需要在 IDE 中配置,在 DevEco Studio 的 build-profile.json5 中指定:

{
  "buildOption": {
    "externalNativeOptions": {
      "path": "./entry/src/main/cpp/CMakeLists.txt",
      "arguments": "-DOHOS_ARCH=arm64-v8a"
    }
  }
}

Q3: 链接 libjemalloc.so 为什么运行时报 undefined

背景: 链接时使用 libjemalloc.so(符号链接),编译成功。运行时 ArkTS 调用 jemalloc.testMallocFree() 报:

TypeError: Cannot read property testMallocFree of undefined

根因: jemalloc 编译时设置了 SONAME 为 libjemalloc.so.2

$ readelf -d libjemalloc.so.2 | grep SONAME
  0x000000000000000e (SONAME) Library soname: [libjemalloc.so.2]

链接时,虽然指定了 libjemalloc.so(符号链接 → libjemalloc.so.2),但链接器记录的 NEEDED 条目是 SONAME 值:

$ strings libentry.so | grep jemalloc
libjemalloc.so.2

OHOS HAP 打包工具将符号链接解析为普通文件复制,只复制了链接名 libjemalloc.so没有复制 SONAME 对应的 libjemalloc.so.2。设备上动态链接器加载 libentry.so 时,根据 NEEDED 查找 libjemalloc.so.2,找不到文件 → libentry.so 加载失败 → import jemalloc from 'libentry.so' 返回 undefined

解决方案: 直接链接 libjemalloc.so.2(真实文件):

# ❌ 链接符号链接(会导致 HAP 缺少 .so.2 文件)
target_link_libraries(entry PUBLIC libjemalloc.so)

# ✅ 链接真实文件(HAP 同时包含 .so 和 .so.2)
target_link_libraries(entry PUBLIC libjemalloc.so.2)

修复后 HAP 中应有:

libs/arm64-v8a/libentry.so
libs/arm64-v8a/libjemalloc.so      # 由 cmake 自动复制
libs/arm64-v8a/libjemalloc.so.2    # ← 运行时必须的文件

Q4: 为什么不直接用系统 malloc/free,要用 jemalloc?

背景: 代码中 extern "C" 声明了 malloc/free 等标准 C 函数名,最终链接到 jemalloc 的实现。

机制:libentry.so 链接 libjemalloc.so 时,动态链接器的符号解析顺序决定了所有 malloc/free 调用跳转到 jemalloc 的实现:

app进程启动 → 加载 libjemalloc.so → 加载 libentry.so
                                          ↓
                 libentry.so 的 malloc 调用 → jemalloc 的实现

无需 PLT hook 或 LD_PRELOAD。jemalloc 导出的 malloc/free 符号自动覆盖系统 libc 的符号(对于链接到 libentry.so 的调用而言)。

Q5: NAPI 模块名 "entry""libentry.so" 的关系是什么?

对应关系:

位置 说明
.nm_modname "entry" NAPI 注册的模块名
.so 文件名 libentry.so 编译产物文件名
ArkTS 导入 import jemalloc from 'libentry.so' 导入路径使用 .so 文件名

OpenHarmony NAPI 框架的约定:ArkTS 导入模块名等于 .so 文件名(不含 lib 前缀和 .so 扩展名)。即 libentry.so 对应 'libentry.so'

注意: .nm_modname 的值在当前版本中不影响导入路径,但建议与模块保持一致,避免混淆。

Q6: ArkTS 侧 import jemalloc from 'libentry.so' 返回 undefined 可能的原因有哪些?

按排查顺序排列:

序号 原因 验证方法
1 libentry.so 依赖的 .so 文件缺失 解压 HAP 检查 libs/arm64-v8a/ 目录
2 SONAME 文件缺失(见 Q3) strings libentry.so | grep jemalloc 查看 NEEDED
3 NAPI Init 函数未注册 检查 .nm_register_func 是否指向正确的 Init 函数
4 NAPI 注册表(property_descriptor)中函数名不匹配 对比 desc[] 中的名称与 ArkTS 调用名
5 libentry.so 依赖于 libc++_shared.so 确保该 .so 在 HAP 中(cmake 默认会复制)

典型排查步骤:

# 1. 检查 HAP 内容
unzip -l entry/build/default/outputs/default/entry-default-signed.hap | grep libs/

# 2. 检查 NEEDED 条目
strings entry/build/default/intermediates/cmake/default/obj/arm64-v8a/libentry.so | grep -E "\.so"

# 3. 检查符号导出
nm -C -D entry/build/default/intermediates/cmake/default/obj/arm64-v8a/libentry.so | grep -E "test|Init"

Q7: ArkTS 中 const { testMallocFree } = jemalloc 为什么编译报错?

背景:runAllTests 函数中尝试使用解构赋值:

// ❌ 编译报错
private runAllTests() {
    const { testMallocFree } = jemalloc;
    this.runTest('malloc/free', () => testMallocFree());
}

报错:

Object may not have 'Symbol.iterator'
Cannot find name 'testMallocFree'

根因: 在 ArkTS 的 struct 类方法中,const 定义的变量如果在箭头函数 () => 中使用,编译器无法正确推断其作用域。这是 ArkTS 对标准 TypeScript 解构语法的限制。

解决方案: 直接通过 jemalloc.xxx 调用,不使用解构:

// ✅ 正确写法
private runAllTests() {
    this.runTest('malloc/free', () => jemalloc.testMallocFree());
    this.runTest('calloc', () => jemalloc.testCalloc());
    // ...
}

Q8: jemalloc 扩展接口(mallocx/dallocx/mallctl)如何使用?

这些是 jemalloc 独有的扩展接口,标准 libc 中没有,需要手动声明:

函数说明:

// mallocx — 带标志的内存分配
// flags 常用值:0(普通分配),MALLOCX_ZERO(0x40,零初始化)
void *mallocx(size_t size, int flags);

// dallocx — 带标志的内存释放
void dallocx(void *ptr, int flags);

// mallctl — jemalloc 控制接口,用于查询/设置内部参数
int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen);

mallctl 查询示例:

unsigned narenas = 0;
size_t sz = sizeof(narenas);
int ret = mallctl("arenas.narenas", &narenas, &sz, nullptr, 0);
// narenas 返回 arena 数量,ret 为 0 表示成功

需要注意: 如果未包含 jemalloc.h,需要手动定义 MALLOCX_ZERO

#define MALLOCX_ZERO ((int)0x40)

Q9: malloc_stats_print 的回调参数如何正确使用?

函数签名:

void malloc_stats_print(
    void (*write_cb)(void *, const char *),
    void *cbopaque,
    const char *opts
);
  • write_cb:回调函数,每次输出一段字符串时调用
  • cbopaque:透传给回调的不透明指针(用于传递用户状态)
  • opts:输出选项,"gJ" 表示打印汇总统计

正确用法示例(C++ lambda 作为回调):

const size_t bufSize = 8192;
char *buffer = (char *)malloc(bufSize);

struct StatsBuffer {
    char *data;
    size_t capacity;
    size_t written;
} statsBuf = {buffer, bufSize, 0};

malloc_stats_print(
    [](void *opaque, const char *str) {
        auto *sb = static_cast<StatsBuffer *>(opaque);
        size_t len = strlen(str);
        size_t remaining = sb->capacity - sb->written;
        if (len < remaining) {
            memcpy(sb->data + sb->written, str, len);
            sb->written += len;
        }
    },
    &statsBuf, "gJ"
);

buffer[statsBuf.written] = '\0';
// 使用 buffer...
free(buffer);

关键注意点:

注意点 说明
回调类型必须是 void (*)(void *, const char *),不能有捕获 用 lambda 时必须是 无捕获 [](可隐式转换为函数指针)
回调中不要调用 malloc 族函数 避免死锁或递归,jemalloc 内部可能在持有锁
opaque 参数传递结构体指针 用于在回调中收集数据,不要使用全局变量
输出内容可能很长(数万字符) 缓存区不够时可动态扩展,或分片读取

Q10: 如何确认 jemalloc .so 导出的符号名?

背景: 需要根据导出的符号名决定是使用 je_malloc 还是 malloc

查看命令:

# 查看所有动态导出的符号
nm -D libjemalloc.so | grep -E " malloc$| free$| calloc$| realloc$"

# 查看所有 C++符号格式(含参数类型)
nm -C -D libjemalloc.so | grep -E " [TF] " | head -20

# 查看 SONAME
readelf -d libjemalloc.so | grep SONAME

输出示例(无前缀导出):

0000000000012345 T malloc
0000000000012346 T free
0000000000012347 T calloc
0000000000012348 T realloc
0000000000012349 T malloc_usable_size
000000000001234a T malloc_stats_print
000000000001234b T mallocx
000000000001234c T dallocx
000000000001234d T mallctl

输出示例(带 je_ 前缀导出):

0000000000012345 T je_malloc
0000000000012346 T je_free
0000000000012347 T je_calloc
...

判断策略:

导出符号 方案
无前缀(malloc 手动 extern "C" 声明,不要包含 jemalloc.h
je_ 前缀 包含 jemalloc.h 正常使用 je_malloc
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐