仓颉标准库 std.runtime 包详解:GC 调优、堆内存统计与运行时监控 API 实战指南

原创2026-10-14 22:19:331,942 阅读
文章标签:编程语言语言运行时标准库内存管理OpenHarmony

仓颉标准库 std.runtime 包详解:GC 调优、堆内存统计与运行时监控 API 实战指南

std.runtime 是仓颉标准库中面向程序运行时环境的统一入口包,提供垃圾回收(GC)手动触发与阈值配置、堆内存统计与快照导出、线程/处理器信息查询以及 CPU 性能剖析等能力。本文以 runtime_package_overview.md 的 API 清单为骨架,结合标准库源码与运行时(cangjie_runtime)底层实现,逐项讲解每个接口的签名、语义、异常与完整调用链路,帮助你写出可运行的 GC 调优与运行时监控代码。

一、std.runtime 包是什么

仓颉语言采用自动垃圾回收机制管理内存,std.runtime 包正是为开发者提供与运行时环境交互能力的工具集,其核心职责包括:

  • 控制程序执行:手动触发垃圾回收、设置 GC 触发阈值;
  • 管理运行时状态:查询堆内存使用量、线程数量、处理器数量;
  • 监控与调优:获取 GC 次数、GC 耗时、GC 回收内存量,导出堆快照,执行 CPU 采样剖析。

从源码目录看,该包由 6 个 .cj 源文件与 1 个 C 原生辅助文件组成(见 stdlib/libs/std/runtime):

源文件 负责的 API 领域
runtime_gc.cj gc / GC / setGCThreshold / getGCCount / getGCTime / getGCFreedSize
runtime_memoryInfo.cj dumpHeapData / 堆内存三指标 / MemoryInfo
runtime_threadInfo.cj 线程统计三函数 / ThreadInfo
runtime_processorInfo.cj getProcessorCount / CPU 剖析 / ProcessorInfo
runtime_signal.cj 信号处理(Signal)
runtime_trace.cj Trace 事件跟踪(Windows/Linux)
native/runtime_ffi.c 处理器数量、Windows 文件描述符等 C 层辅助

需要特别说明的是,包中大部分 API 都带有 @When<a href="https://link.gitcode.com/i/58b4e95c8bf6b5629eb0fccedc2ac1b8" target="_blank">backend == "cjnative"] 条件编译标注,即仅在 cjnative(仓颉原生)后端下可用;getProcessorCount 则为 @FastNative,通过 C 函数 CJ_OS_ProcessorCount() 获取处理器数量(见 [runtime_processorInfo.cj)。

二、垃圾回收(GC)控制与调优

2.1 手动触发 GC:gc(Bool) 与已废弃的 GC(Bool)

函数签名(见 runtime_package_funcs.md):

public func gc(heavy!: Bool = false): Unit
  • 参数 heavy!:GC 执行的强度。true 时执行更慢但回收更多内存;默认值为 false。
  • GC(Bool)(已废弃):旧命名的大写版本,签名与 gc 完全一致。文档明确标注"未来版本将废弃,请改用 gc(Bool)"。

从 runtime_gc.cj 源码可以看到废弃 API 的实现方式——它只是对 gc 的转发:

@Deprecated[message: "Use 'public func gc(heavy!: Bool = false): Unit' instead."]
public func GC(heavy!: Bool = false): Unit {
    return gc(heavy: heavy)
}

public func gc(heavy!: Bool = false): Unit {
    unsafe { return invokeGC(heavy) }
}

其中 invokeGC 是编译器内建函数(@Intrinsic),最终落到运行时 C 层 CompilerCalls.cpp 的 MCC_InvokeGCImpl,其实现为:

extern "C" void MCC_InvokeGCImpl(bool sync) { HeapManager::RequestGC(GC_REASON_USER, !sync); }

也就是说,一次 gc() 调用实际是通过 std.runtime.gc → invokeGC(Intrinsic)→ MCC_InvokeGCImpl → HeapManager::RequestGC(GC_REASON_USER) 的链路触发一次用户原因(GC_REASON_USER)的垃圾回收。

2.2 设置 GC 触发阈值:setGCThreshold(UInt64)

函数签名:

public func setGCThreshold(value: UInt64): Unit
  • 参数 value:以 KB 为单位的用户自定义内存阈值。当仓颉堆大小超过该值时触发 GC。

文档给出的示例——将 GC 内存阈值设为 2MB:

import std.runtime.*
main() {
  setGCThreshold(2048)
}

对应地,旧版大写 API SetGCThreshold(UInt64) 已废弃,示例同样为 SetGCThreshold(2048)(见 runtime_package_funcs.md)。源码 runtime_gc.cj 中两者都标注了 @Deprecated[message: "..."] 或直接转发到新函数。

阈值最终写入运行时参数。在 CangjieRuntime.h 中可以看到单位换算与合法性校验:

void SetGCThreshold(uint64_t threshold) override
{
    if (threshold == 0) {
        LOG(RTLOG_ERROR, "The threshold must be greater than 0.\n");
    } else {
        param.gcParam.gcThreshold = threshold * KB;
        LOG(RTLOG_INFO, "gcThreshold is set to %d KB.", threshold);
    }
}

即:传入的 KB 数值会被乘以 KB 常量换算为字节存入 gcParam.gcThreshold;若传入 0 会被拒绝(仅记录错误日志)。这正是后续 GC 触发判定所参考的阈值。

2.3 GC 运行状态与统计查询

该组 API 用于 GC 调优与监控,全部返回 Int64:

函数 返回含义
getGCCount() GC 触发次数
getGCTime() GC 总耗时(微秒)
getGCFreedSize() GC 成功后回收的内存总量(字节)

底层实现(CompilerCalls.cpp)直接读取运行时维护的全局统计量:

extern "C" size_t MCC_GetGCCount() { return g_gcCount; }
extern "C" uint64_t MCC_GetGCTimeUs() { return g_gcTotalTimeUs; }
extern "C" size_t MCC_GetGCFreedSize() { return g_gcCollectedTotalBytes; }

此外,runtime_gc.cj 还提供了一个未列入概览表的辅助函数 isGCRunning()(仅 cjnative 且非 ohos 环境),它通过 @FastNative 调用 CJ_MCC_IsGCRunning()(见 CommonAlias.h),底层对应 CompilerCalls.cpp 的 Heap::GetHeap().IsGcStarted(),可用于判断 GC 是否正在进行。

三、堆内存统计与堆快照导出

3.1 三大内存指标

std.runtime 提供三个无参函数获取仓颉堆内存信息(见 runtime_package_funcs.md):

函数 返回含义
getAllocatedHeapSize() 仓颉堆已分配大小(字节)
getUsedHeapSize() Linux:仓颉堆实际占用物理内存;Windows/macOS:仓颉进程实际占用物理内存(字节)
getMaxHeapSize() 仓颉堆可用的最大堆大小(字节)

底层实现(CompilerCalls.cpp):

extern "C" ssize_t MCC_GetRealHeapSize() { return Heap::GetHeap().GetHeapPhysicalMemorySize(); }
extern "C" size_t MCC_GetAllocatedHeapSize() { return Heap::GetHeap().GetAllocatedSize(); }
extern "C" size_t MCC_GetMaxHeapSize() { return Heap::GetHeap().GetMaxCapacity(); }

值得注意的异常行为:getUsedHeapSize() 在 runtime_memoryInfo.cj 中会对底层返回值做错误码映射,返回 -1 时抛出 MemoryInfoException("Failed to open smaps file.")(Linux 下读取 /proc/self/smaps 失败),返回 -2 时抛出 MemoryInfoException("Failed to get valid value."):

public func getUsedHeapSize(): Int64 {
    var ret = Int64(getRealHeapSize())
    match {
        case ret >= 0 => return ret
        case ret == -1 => throw MemoryInfoException("Failed to open smaps file.")
        case ret == -2 => throw MemoryInfoException("Failed to get valid value.")
        case _ => throw MemoryInfoException("Failed to get valid error code.")
    }
}

3.2 堆快照导出:dumpHeapData(Path)

public func dumpHeapData(path: Path): Unit
  • 参数 path:Path 类型,堆快照文件的写出路径。
  • 异常:生成失败时抛出 MemoryInfoException。

该函数实现(runtime_memoryInfo.cj)包含三重安全防护:

  1. 路径合法性校验:validatePathString 拒绝空路径、超过 4096 字符的路径,以及包含 \0 空字节的路径(防止路径截断/注入攻击);
  2. 路径解析:resolvePath 将相对路径基于当前目录解析为绝对路径,并处理符号链接(FileInfo(candidatePath).isSymbolicLink() 时执行 canonicalize);
  3. 文件描述符打开:非 Windows 平台通过 File(resolvedPath, ReadWrite) 拿到 fd 后调用内建 dumpCjHeapData(fd);Windows 平台则通过 executeWithWindowsFd 借助 C 函数 CJ_Runtime_OpenFileForFd / CJ_Runtime_CloseFd(见 runtime_ffi.c,使用 _wopen 以宽字符路径打开文件)完成写入。

写入失败时同样抛出 MemoryInfoException("Failed to dump heap data.")。底层对应 CompilerCalls.cpp 的 MCC_DumpCJHeapData,它会进入安全区(ScopedEnterSaferegion)并调用 CjHeapData::DumpHeap(fd) 生成堆快照数据。

四、线程与处理器信息查询

该组函数用于观察仓颉运行时并发状态:

函数 返回含义
getThreadCount() 当前仓颉线程数量
getBlockingThreadCount() 阻塞中的仓颉线程数量
getNativeThreadCount() 物理线程数量
getProcessorCount() 处理器数量

源码实现(runtime_threadInfo.cj)通过 @Intrinsic 函数 getThreadNumber / getBlockingThreadNumber / getNativeThreadNumber 取值,底层对应(CompilerCalls.cpp):

extern "C" size_t MCC_GetCJThreadNumber() { return ScheduleCJThreadCountPublic(CJTHREAD_PSTATE_ALL); }
extern "C" size_t MCC_GetBlockingCJThreadNumber() { return ScheduleCJThreadCountPublic(CJTHREAD_PSTATE_BLOCKING); }
extern "C" size_t MCC_GetNativeThreadNumber() { return ScheduleRunningOSThreadCount(); }

getProcessorCount() 则经由 @FastNative 调用 CJ_OS_ProcessorCount()(runtime_ffi.c):Linux 下使用 sysconf(_SC_NPROCESSORS_ONLN),macOS 下使用 sysctl(CTL_HW, HW_AVAILCPU)(失败时回退到 HW_NCPU),Windows 下使用 GetSystemInfo 的 dwNumberOfProcessors。

五、CPU 性能剖析

public func startCPUProfiling(): Unit
public func stopCPUProfiling(path: Path): Unit
  • 配对使用:startCPUProfiling 与 stopCPUProfiling(Path) 必须成对调用。
  • 异常:未先调用 stopCPUProfiling 就再次调用 startCPUProfiling 时抛出 ProfilingInfoException;同样,未调用 startCPUProfiling 就调用 stopCPUProfiling 也会抛出 ProfilingInfoException。

源码实现(runtime_processorInfo.cj):startCPUProfiling 调用内建 startCjCPUProfiling(),失败即抛 ProfilingInfoException("Failed to start cpu profiling.");stopCPUProfiling(path) 打开指定文件并调用 stopCjCPUProfiling(fd) 把采样记录写入该文件,Windows 平台复用 executeWithWindowsFd 路径。

运行时侧的采样机制在 CpuProfiler.cpp 中实现:StartCpuProfilerForFile 会启动一条独立的采样线程 SamplingThread,以固定间隔(SamplesRecord::GetSamplingInterval())调用 DoSampleStack(即 MutatorManager::Instance().TransitionAllMutatorsToCpuProfile())采集各 mutator 线程的调用栈;StopCpuProfilerForFile(fd) 先打开输出文件,再停止采样线程,并将采集到的样本数据 DumpProfileInfo() 写入文件。

六、已废弃的统计结构体及迁移指南

概览表中的三个结构体均标注为 deprecated,官方建议改用全局函数替代(详见 runtime_package_structs.md):

结构体 静态属性 迁移替代函数
MemoryInfo(废弃) allocatedHeapSize getAllocatedHeapSize()
heapPhysicalMemory getUsedHeapSize()
maxHeapSize getMaxHeapSize()
ProcessorInfo(废弃) processorCount getProcessorCount()
ThreadInfo(废弃) blockingThreadCount getBlockingThreadCount()
nativeThreadCount getNativeThreadCount()
threadCount getThreadCount()

文档给出的旧式用法示例(输出随环境变化):

import std.runtime.*
main() {
  println(MemoryInfo.maxHeapSize)
}
268435456

源码中的废弃标注(runtime_memoryInfo.cj)为 @Deprecated[message: "All static Properties are converted to public functions."],且 MemoryInfo 的属性内部正是转调全局函数(如 heapPhysicalMemory 的 getter 直接返回 getUsedHeapSize()),因此迁移到新函数不会产生任何行为差异。

七、包内扩展能力:信号处理与 Trace 跟踪

除概览表列出的 API 外,std.runtime 包还包含两类运行时周边能力,属于同一包内模块,此处作简要指引:

  • 信号处理(Signal):Signal 类预定义了 SIGHUP(1)、SIGINT(2)、SIGQUIT(3)、SIGTRAP(5)、SIGALRM(14)、SIGTERM(15) 等常量(见 runtime_package_class.md),并可通过 registerSignalHandler / unregisterSignalHandler / resetSignalHandler 注册、注销、重置信号回调链;回调类型为 SignalHandlerFunc = (Int32) -> Bool,返回 true 表示已处理并停止后续回调,false 则继续执行直到最后一个回调,若最后仍返回 false 将执行信号默认行为。注意该类能力不支持 Windows。完整的可运行示例(含发送信号、链式回调与默认行为对比)见 runtime_sample.md。
  • Trace 跟踪:仅 Windows/Linux 平台支持,Trace.start(TraceType.all, out) 可开启调度/运行时事件跟踪(TraceType 支持 schedule、runtime、all 及 | 组合),Trace.stop() 停止跟踪;也支持通过环境变量 cjTracePath 在进程启动时自动开启跟踪(见 runtime_trace.cj)。

八、实战小结:一套完整的运行时监控示例

结合上述 API,可以组合出一个典型的 GC 监控代码段:

import std.runtime.*

main() {
    // 设置 GC 触发阈值(单位 KB),例如 8MB
    setGCThreshold(8192)

    // 监控性触发一次 GC
    gc(heavy: true)

    // 读取 GC 统计
    println("GC count: ${getGCCount()}")
    println("GC time(us): ${getGCTime()}")
    println("GC freed(bytes): ${getGCFreedSize()}")

    // 读取堆内存与线程/处理器信息
    println("max heap(bytes): ${getMaxHeapSize()}")
    println("allocated heap(bytes): ${getAllocatedHeapSize()}")
    println("used heap(bytes): ${getUsedHeapSize()}")
    println("threads: ${getThreadCount()}, blocked: ${getBlockingThreadCount()}, native: ${getNativeThreadCount()}")
    println("processors: ${getProcessorCount()}")
}

使用时请注意以下几点:

  1. 大部分 API 仅对 cjnative 后端生效,跨平台行为以 stdlib/libs/std/runtime 中各文件的 @When 标注为准;
  2. getUsedHeapSize() 在 Linux 与 Windows/macOS 上的统计口径不同(堆物理内存 vs 进程物理内存);
  3. gc / setGCThreshold 请使用小写新命名,避免触发编译器的废弃告警;
  4. dumpHeapData / stopCPUProfiling 涉及文件写入,注意目标路径的合法性(空路径、超长路径、\0 字符会被拒绝);
  5. startCPUProfiling 与 stopCPUProfiling(Path) 必须严格成对,否则抛出 ProfilingInfoException。

以上 API 的签名与语义以 runtime_package_funcs.md 和 runtime_package_structs.md 为准,底层行为可对照 runtime/src/CompilerCalls.cpp、runtime/src/CpuProfiler/CpuProfiler.cpp 与 runtime/src/CangjieRuntime.h 深入研读。

登录后查看全文
cangjie_runtime