ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

JerryScript 引用计数机制指南:jerry_value_t 生命周期与内存管理实战

JerryScript 引用计数机制指南:jerry_value_t 生命周期与内存管理实战 语言运行时嵌入式物联网编译器【免费下载链接】jerryscriptUltra-lightweight JavaScript engine for the Internet of Things.项目地址https://gitcode.com/gh_mirrors/je/jerryscript点击查看免费下载JerryScript 是一款面向物联网IoT的超轻量级 JavaScript 引擎其嵌入式场景对内存占用极为敏感因此 API 层围绕引用计数设计了严格的生命周期规则。本文以 docs/06.REFERENCE-COUNTING.md 为骨架结合 jerry-core/api/jerryscript.c 与 jerry-core/include/jerryscript-core.h 等源码实现系统讲解jerry_value_t的引用语义、释放规则、错误路径处理与外部函数回调的引用转移。读完本文你将能够写出无内存泄漏、无悬垂引用的 JerryScript 嵌入代码并理解底层引用计数与垃圾回收的协作原理。一、jerry_value_t独立的活引用1.1 值句柄的底层本质在 JerryScript 中所有 JS 值经由 C API 暴露时都统一表现为jerry_value_t类型。从源码看该类型是一个uint32_t的句柄见 jerry-core/include/jerryscript-types.h它并不直接承载 JS 对象而是指向引擎内部对象的引用。引用计数的底层实现在 jerry-core/ecma/base/ecma-gc.c每个内部对象都维护一个引用计数ecma_ref_object_inline第 132-143 行负责增加引用计数ecma_deref_object第 157-162 行负责减少引用计数。当引用计数降到零对象才会被垃圾回收器回收。特别地如果引用计数达到上限ECMA_OBJECT_MAX_REF引擎会调用jerry_fatal (JERRY_FATAL_REF_COUNT_LIMIT)直接致命退出该错误信息定义于 jerry-core/jrt/jrt-fatals.c——可见引用计数管理的正确性对引擎稳定性至关重要。1.2 核心规则返回值必为活引用JerryScript API 返回的每一个jerry_value_t都是独立的活引用必须由调用方通过jerry_value_free释放。文档给出的全局对象示例jerry_value_t global jerry_current_realm (); /* global 变量中保存着对全局对象的一个活引用。 * 引擎内部也持有自己的活引用两者相互独立。 * 必须两个引用都被销毁全局对象才能被释放。 */ jerry_value_free (global); /* 若不调用 jerry_value_free()即使调用 jerry_cleanup() * 全局对象也不会被释放。引用被释放后即成为死引用不能再使用。 */jerry_current_realm的声明见 jerry-core/include/jerryscript-core.h它返回当前 realm 的全局对象。注意注释强调引擎内部引用与 API 返回的引用是两回事释放 API 引用不会影响引擎自身运行所需的引用反之亦然。二、同一内部对象的多重引用多个jerry_value_t可以指向同一个内部对象即便它们在 C 层面的表示句柄数值可能不同jerry_value_t pi_ref1 jerry_number (3.14); jerry_value_t pi_ref2 jerry_value_copy (pi_ref1); /* pi_ref1 与 pi_ref2 指向同一个 3.14 值 * 虽然它们在 C 中可能不相等pi_ref1 ! pi_ref2。 */ /* 两个引用都必须分别释放。 */ jerry_value_free (pi_ref1); jerry_value_free (pi_ref2);从实现上看jerry_value_copyjerry-core/api/jerryscript.c内部调用ecma_copy_value为底层对象增加引用计数从而产生一个新的独立引用对于异常值则会走ecma_ref_extended_primitive分支。因此jerry_value_copy是“按引用复制”对复制品所做的修改会反映到原值上。三、禁止重复释放释放同一个jerry_value_t两次是不允许的因为同一句柄代表同一个引用重复释放意味着对同一引用执行两次ecma_deref_object会破坏引用计数平衡可能引发崩溃。文档明确指出以下写法是错误示范jerry_value_free (pi_ref1); jerry_value_free (pi_ref1); /* 错误同一引用被释放两次 */从底层看ecma_deref_object内部有JERRY_ASSERT (object_p-type_flags_refs ECMA_OBJECT_REF_ONE)断言jerry-core/ecma/base/ecma-gc.c在调试构建下重复释放会直接触发断言失败。四、传参不转移所有权将jerry_value_t传给 API 函数绝不会释放它的引用除非文档明确说明。这意味着调用方传给函数的引用在函数返回后依然有效仍须由调用方释放函数内部需要保留该值时会自行创建新的引用不消耗传入引用的计数。下面以属性读取/写入为例完整演示这一行为jerry_object_get与jerry_object_set的声明见 [jerry-core/include/jerryscript-core.h](https://link.gitcode.com/i/6edc9f6296d14b5f1155a3caf635b797#L583 与 #L552)jerry_value_t prop_value jerry_object_get (...); /* prop_value 必须在之后释放因为基对象和 prop_value * 各自持有指向同一 JS 值的独立引用。若操作失败 * prop_value 中保存的是对错误对象的活引用同样必须释放。 */ if (jerry_value_is_exception (prop_value)) { /* 在此处理错误。 */ } else { /* 即使基对象被垃圾回收器回收 * 应用仍持有对属性值的活引用。 */ } /* prop_value 必须被释放。 */ jerry_value_free (prop_value); /* 属性写入同理。 */ jerry_value_t new_prop_value jerry_number (2.718); jerry_value_t result jerry_object_set (..., new_prop_value); /* 若属性写入成功引擎会为 new_prop_value 所指的值创建新引用。 * 无论操作是否成功new_prop_value 引用都必须释放。 */ /* 在调用 jerry_value_free () 之前 * new_prop_value 可以继续传给其他 JerryScript API 函数。 */ jerry_value_free (new_prop_value); /* result 中的引用无论操作成功与否都是活引用也必须释放。 */ if (jerry_value_is_exception (result)) { /* 在此处理错误。 */ } else { /* 返回的是指向 true 原始值的引用。 */ } jerry_value_free (result);这段代码揭示了 JerryScript 引用模型中最容易出错的两个点读取属性jerry_object_get返回的引用与基对象内部持有的引用相互独立无论成功与否都必须释放失败时返回的是异常对象的活引用不能因为“出错”就跳过释放。写入属性new_prop_value与result是两个不同的引用需要分别释放jerry_object_set内部为属性值创建新引用不会消耗传入的new_prop_value。如果完全不需要错误处理最简单紧凑的写法是/* 这段代码里没有任何 if。 */ jerry_value_free (jerry_object_set (..., new_prop_value)); jerry_value_free (new_prop_value);jerry_object_set的返回值直接传给jerry_value_free一行完成“调用 释放返回值”两个动作在嵌入式场景下可显著压缩代码体积。五、外部处理函数external handler的引用转移jerry_external_handler_t类型定义于 jerry-core/include/jerryscript-types.h是用 C 实现 JS 函数的入口。其引用规则是整个模型中唯一“转移所有权”的场景jerry_value_t my_external_handler (const jerry_value_t function_obj, const jerry_value_t this_val, const jerry_value_t args_p[], const jerry_length_t args_count) { /* 不要释放 function_obj、this_val 和 args_p * 这些引用在 handler 返回后会被引擎自动释放。 * 这种设计是为了减小代码体积对嵌入式系统很友好。 * 如有需要可以用 jerry_value_copy () 为它们创建新引用。 */ /* 由于返回值的引用所有权转移给调用方下面的写法是合法的。 */ /* 如果返回的值还要另作他用 * 可以用 jerry_value_copy () 创建新引用。 */ return jerry_string (...); }关键语义入参function_obj、this_val、args_p[]引擎负责释放handler 内不得调用jerry_value_free否则会造成重复释放返回值handler 返回的引用其所有权转移给引擎/调用方引擎不会额外复制若返回一个自己创建的引用如jerry_string的返回值那正是“创建即转移”无需也不能再释放否则被引用的对象可能在垃圾回收时被提前释放需要保留入参用jerry_value_copy复制出新引用即可这与第四节“传参不转移所有权”的规则保持一致。jerry_string的声明见 jerry-core/include/jerryscript-core.h返回值为新创建的字符串引用。六、C 层面的复制不产生新引用6.1 简单赋值共享同一引用在 C 中把一个jerry_value_t赋值给另一个变量不会创建新的活引用jerry_value_t undef jerry_undefined (); jerry_value_t undef2 undef; /* 释放 undef 或 undef2 都是合法的但不能两者都释放。 * 释放后两个引用都变为死引用无效。 */ jerry_value_free (undef2); /* 死引用可以重新赋值。 */ undef jerry_boolean (true);jerry_undefined的声明见 jerry-core/include/jerryscript-core.h。这里undef与undef2本质是同一个引用的两份拷贝只对应一次引用计数因此只能释放一次释放后两者都失效但变量本身可以重新赋值使用。6.2 多变量共享只释放其中一个只要保证“一个引用只释放一次”在 C 中就可以放心地让多个变量共享同一个引用jerry_value_t a jerry_boolean (true); jerry_value_t b a; jerry_value_t c a; /* 给 a 赋一个新引用。 */ a jerry_boolean (false); [...] jerry_value_free (a); /* a布尔 false的引用变为死引用无效。 */ jerry_value_free (c); /* b 和 c布尔 true的引用都变为死引用。 */ /* 所有引用均已释放不会发生内存泄漏。 */这里的关键在于b和c共享a最初持有的那一个引用释放c即可重新赋值后的a持有一个新引用需要单独释放。两次释放分别对应两个独立引用计数平衡无泄漏。七、释放 API 的底层实现所有引用最终都汇聚到 jerry-core/api/jerryscript.c 的jerry_value_freevoid jerry_value_free (jerry_value_t value) { jerry_assert_api_enabled (); if (JERRY_UNLIKELY (ecma_is_value_exception (value))) { ecma_deref_exception (ecma_get_extended_primitive_from_value (value)); return; } ecma_free_value (value); }可见释放逻辑分为两条路径异常值调用ecma_deref_exception释放异常对象这也印证了第四节中“出错时返回的异常引用同样要释放”的结论普通值调用ecma_free_value其内部依据值类型走引用计数递减对象或直接释放原始值的不同分支。这与jerry_value_copyjerry-core/api/jerryscript.c的复制路径一一对称复制时ecma_copy_value/ecma_ref_extended_primitive增加计数释放时ecma_free_value/ecma_deref_exception减少计数。一增一减保持平衡正是整个引用计数模型自洽的基石。八、工程实践建议8.1 内存泄漏与悬垂引用自查清单场景正确做法常见错误API 返回的jerry_value_t每个返回值都必须jerry_value_free忘记释放泄漏jerry_cleanup也无法回收失败/异常返回值异常引用同样是活引用必须释放因“出错”而跳过释放jerry_value_copy复制品每个复制品单独释放只释放原引用C 赋值共享引用只释放其中一份释放两份计数失衡可能崩溃external handler 入参不释放引擎自动处理手动jerry_value_freeexternal handler 返回值所有权转移直接返回新引用返回后再次释放释放后的引用视为死引用不再使用可重新赋值继续读写死引用8.2 测试用例佐证仓库单元测试对引用计数规则有大量直接印证。例如 tests/unit-core/test-abort.c 中对res、func、callback_name、global等 API 返回值逐一调用jerry_value_free后再继续后续操作tests/unit-core/test-api-binary-operations-arithmetics.c 则展示了在二元运算场景下用jerry_value_copy为每个操作数创建独立引用、并在结束后逐一对lhs、rhs、result释放第 113-115 行的完整模式。阅读这些测试是掌握引用纪律最直观的途径。8.3 内存受限平台的额外提示JerryScript 面向 IoT 场景内存预算通常以 KB 计。在编写嵌入代码时建议尽量使用“调用 立即释放”的单行模式如jerry_value_free (jerry_object_set (...))缩短引用的存活窗口在长生命周期循环中及时释放不再使用的中间值避免引用堆积导致引擎无法回收对象优先复用jerry_value_copy而非重复调用会产生新引用的构造 API调试阶段可开启断言构建让ecma_deref_object中的引用计数断言jerry-core/ecma/base/ecma-gc.c尽早暴露重复释放问题。总结JerryScript 的引用计数模型可以浓缩为四条规则API 返回值都是活引用、必须由调用方释放传参不转移所有权external handler 返回值转移所有权、入参由引擎释放C 复制不产生新引用。只要把握住“引用与内部对象一一对应、释放次数必须与引用创建次数严格相等”这一本质配合jerry_value_copy与jerry_value_free的对称实现见 jerry-core/api/jerryscript.c就能在嵌入式资源受限环境下写出既省内存又稳定可靠的 JerryScript 嵌入层代码。赞分享语言运行时嵌入式物联网编译器【免费下载链接】jerryscriptUltra-lightweight JavaScript engine for the Internet of Things.项目地址https://gitcode.com/gh_mirrors/je/jerryscript点击查看免费下载相关推荐彻底解决Skia内存泄漏从引用计数到生命周期管理实战指南彻底解决Skia内存泄漏从引用计数到生命周期管理实战指南 Skia作为Google开源的2D图形库在Chrome、Android等众多项目中广泛应用但内存图形学图像处理IBAnimatable 内存管理最佳实践弱引用与生命周期管理IBAnimatable 内存管理最佳实践弱引用与生命周期管理 在iOS开发中内存管理是保证应用性能和稳定性的关键环节。IBAnimatable作为一个专注移动开发UI组件TTTAttributedLabel高级内存管理弱引用与生命周期TTTAttributedLabel高级内存管理弱引用与生命周期 内存管理核心痛点解析 在iOS开发中UILabel的扩展组件常因内存管理不当导致循环引用。UI组件移动开发上一篇深度解析Flux1-Dev-Bnb-Nf4模型的配置与环境要求下一篇Project Graph文件格式详解为什么.prg是分享节点图的最佳选择创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表