仓颉标准库 std.runtime 包详解:GC 调优、堆内存统计与运行时监控 API 实战指南
仓颉标准库 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)包含三重安全防护:
- 路径合法性校验:
validatePathString拒绝空路径、超过 4096 字符的路径,以及包含\0空字节的路径(防止路径截断/注入攻击); - 路径解析:
resolvePath将相对路径基于当前目录解析为绝对路径,并处理符号链接(FileInfo(candidatePath).isSymbolicLink()时执行canonicalize); - 文件描述符打开:非 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()}")
}
使用时请注意以下几点:
- 大部分 API 仅对
cjnative后端生效,跨平台行为以 stdlib/libs/std/runtime 中各文件的@When标注为准; getUsedHeapSize()在 Linux 与 Windows/macOS 上的统计口径不同(堆物理内存 vs 进程物理内存);gc/setGCThreshold请使用小写新命名,避免触发编译器的废弃告警;dumpHeapData/stopCPUProfiling涉及文件写入,注意目标路径的合法性(空路径、超长路径、\0字符会被拒绝);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 深入研读。