GraalVM Native Image C API 完整指南:用 C/C++ 管理 Java 对象与 Isolate 生命周期
【免费下载链接】graalGraalVM compiles applications into native executables that start instantly, scale fast, and use fewer compute resources 🚀项目地址: https://gitcode.com/gh_mirrors/gr/graal
本指南以 GraalVM 仓库中 C-API.md 文档为核心,系统讲解 Native Image 的 C 语言 API:如何从 C/C++ 侧创建 isolate、附加与分离线程、查询线程与 isolate 的对应关系以及安全地拆除 isolate。文中所有头文件声明均可溯源至仓库内的 graal_isolate.preamble 与其对应的 Java 实现 CEntryPointNativeFunctions.java,读者阅读后可直接在自己的共享库集成代码中正确使用这套 API。
什么是 Native Image C API
Native Image 提供了一套 GraalVM 特有的 C 语言 API,用于:
- 从 C/C++ 语言中创建和管理 isolate(GraalVM 的隔离运行时实例,每个 isolate 拥有独立的堆与运行时状态);
- 初始化 isolate并将宿主线程**附加(attach)**到 isolate 上;
- 获取当前线程对应的
graal_isolatethread_t、反查线程所属的 isolate; - 在线程不再需要时将其分离(detach),并在合适时机**拆除(tear down)**整个 isolate。
这套 API 的可用前提是:Native Image 以共享库(shared library)形式构建。构建过程中会自动生成对应的头文件,所有 C API 声明都包含在该头文件中。也就是说,只有当你把包含@CEntryPoint入口方法的 Java 类编译为动态链接库(.so/.dylib/.dll)时,生成的graal_isolate.h风格头文件里才会携带这些声明。
从源码结构看,该头文件由 GraalIsolateHeader.java 驱动生成:它以@CHeader(value = GraalIsolateHeader.class)的形式挂在 CEntryPointNativeFunctions.java 上,其writePreamble方法会把 graal_isolate.preamble 中的内容原样写入最终头文件。因此,下文给出的结构体定义与 API 原型均可在仓库源码中直接核对。
核心数据结构:isolate 与 isolate thread
不透明句柄类型
/* * Structure representing an isolate. A pointer to such a structure can be * passed to an entry point as the execution context. */ struct __graal_isolate_t; typedef struct __graal_isolate_t graal_isolate_t; /* * Structure representing a thread that is attached to an isolate. A pointer to * such a structure can be passed to an entry point as the execution context, * requiring that the calling thread has been attached to that isolate. */ struct __graal_isolatethread_t; typedef struct __graal_isolatethread_t graal_isolatethread_t;graal_isolate_t:isolate 的句柄。一个 isolate 对应一个独立的 GraalVM 运行时实例(独立的堆、类加载与执行状态)。它的指针可以作为入口方法(entry point)的执行上下文传入。graal_isolatethread_t:已附加到某 isolate 的线程句柄。调用入口方法时,前提是该调用线程已经附加到了对应 isolate;此时把该句柄作为执行上下文传入即可。
两者均为不透明结构体(opaque struct),C 侧无需也不应关心其内部布局,只需传递与保存指针。
辅助类型__graal_uword
preamble 中还定义了参数结构体使用的无符号字类型:
#ifdef _WIN64 typedef unsigned long long __graal_uword; #else typedef unsigned long __graal_uword; #endif即 64 位 Windows 上为unsigned long long,其余平台为unsigned long,宽度均为机器字长,用于表达地址空间大小等以字节为单位的量。
保护域常量
#define NO_PROTECTION_DOMAIN 0 #define NEW_PROTECTION_DOMAIN -1这两个常量对应graal_create_isolate_params_t.pkey字段:0表示该 isolate 不属于任何保护域,-1表示为它新建一个保护域。该字段属于内部用法,普通集成代码保持默认即可。
创建 isolate 的参数结构体graal_create_isolate_params_t
/* Parameters for the creation of a new isolate. */ enum { __graal_create_isolate_params_version = 5 }; struct __graal_create_isolate_params_t { /* Version of this struct. Set to __graal_create_isolate_params_version after zeroing this struct. */ int version; /* Fields introduced in version 1 */ __graal_uword reserved_address_space_size; /* Size of virtual address space to reserve for the heap. */ /* Fields introduced in version 2. Internal usage, do not use. */ const char *auxiliary_image_path; /* Path to an auxiliary image to load. */ __graal_uword auxiliary_image_reserved_space_size; /* Reserved bytes for loading an auxiliary image. */ /* Fields introduced in version 3 */ int argc; /* Number of char* argument strings in argv. */ char **argv; /* Array of argument strings, parsed like command line arguments. */ int pkey; /* Isolate protection key or domain. Internal usage, do not use. */ /* Fields introduced in version 4 */ char ignore_unrecognized_args; /* Ignore unrecognized arguments in argv when 1. */ char _reserved_4; /* Internal usage, do not use. */ /* Fields introduced in version 5 */ char _reserved_5; /* Internal usage, do not use. */ }; typedef struct __graal_create_isolate_params_t graal_create_isolate_params_t;字段说明与使用建议:
| 字段 | 引入版本 | 语义 | 使用建议 |
|---|---|---|---|
version | 初始 | 结构体版本号,必须置为__graal_create_isolate_params_version(当前为 5) | 必填。正确的做法是先把整个结构体清零,再写入版本号 |
reserved_address_space_size | 1 | 为堆预留的虚拟地址空间大小(字节) | 可选;按需预留较大地址空间可减少动态扩展开销 |
auxiliary_image_path | 2 | 要加载的辅助镜像(auxiliary image)路径 | 内部使用,不要使用 |
auxiliary_image_reserved_space_size | 2 | 加载辅助镜像预留的字节数 | 内部使用,不要使用 |
argc | 3 | argv中char*参数的个数 | 可选;类似命令行参数解析 |
argv | 3 | 参数字符串数组,像命令行参数一样被解析 | 可选;可用于向 isolate 传递运行参数 |
pkey | 3 | isolate 保护键或保护域 | 内部使用,不要使用 |
ignore_unrecognized_args | 4 | 置 1 时忽略argv中无法识别的参数 | 可选;传 1 可避免未识别参数导致创建失败 |
_reserved_4/_reserved_5 | 4 / 5 | 保留字段 | 保留,置 0 即可 |
该结构体具有显式的版本演进机制:每一版新增字段都做了标注,保持二进制兼容。创建 isolate 时若不需要任何参数,可以直接传NULL。
典型的初始化写法:
graal_create_isolate_params_t params; memset(¶ms, 0, sizeof(params)); /* 先清零 */ params.version = __graal_create_isolate_params_version; /* 再设版本号 */ params.reserved_address_space_size = 0; /* 按需设置 */ params.ignore_unrecognized_args = 1; /* 例如容忍未知参数 */完整 C API 函数参考
以下六个函数构成 C API 的主体,每个函数都有对应的 Java 侧@CEntryPoint实现(见 CEntryPointNativeFunctions.java),二者通过nameTransformation = NameTransformation.class将 Java 方法名转换为 C 导出符号名(create_isolate、attach_thread等)。
1. 创建 isolate:graal_create_isolate
int graal_create_isolate(graal_create_isolate_params_t* params, graal_isolate_t** isolate, graal_isolatethread_t** thread);- 创建一个新 isolate,考虑传入的参数(可以为
NULL); - 成功返回
0,失败返回非零值; - 成功后,当前线程会自动附加到新创建的 isolate,并且 isolate 地址与 isolate thread 地址分别写入传入的指针(若指针非
NULL)。
对应 Java 实现(CEntryPointNativeFunctions.java):
@CEntryPoint(name = "create_isolate", ...) public static int createIsolate(CEntryPointCreateIsolateParameters params, IsolatePointer isolate, IsolateThreadPointer thread) { int result = CEntryPointActions.enterCreateIsolate(params); if (result != 0) { return result; } if (isolate.isNonNull()) { isolate.write(CurrentIsolate.getIsolate()); } if (thread.isNonNull()) { thread.write(CurrentIsolate.getCurrentThread()); } return CEntryPointActions.leave(); }可见其核心调用链是enterCreateIsolate→ 写入 isolate / thread 句柄 →leave,与头文件文档注释完全一致。
2. 附加线程:graal_attach_thread
int graal_attach_thread(graal_isolate_t* isolate, graal_isolatethread_t** thread);- 将当前线程附加到传入的 isolate;
- 失败返回非零值;成功时把创建的 isolate thread 结构地址写入传入指针并返回
0; - 幂等性:如果该线程已经附加过,调用依然成功,并同样返回其 isolate thread 结构。
这一点在实现 CEntryPointNativeFunctions.java 中体现为CEntryPointActions.enterAttachThread(isolate, true)。
3. 获取当前线程句柄:graal_get_current_thread
graal_isolatethread_t* graal_get_current_thread(graal_isolate_t* isolate);- 给定一个当前线程已附加的 isolate,返回该线程对应的 isolate thread 结构地址;
- 若当前线程未附加到该 isolate,或发生其他错误,返回
NULL。
4. 反查所属 isolate:graal_get_isolate
graal_isolate_t* graal_get_isolate(graal_isolatethread_t* thread);- 给定 isolate thread 结构,返回其所属 isolate 的结构地址;
- 出错时返回
NULL。
实现上直接读取线程局部数据VMThreads.IsolateTL.get(thread)(见 CEntryPointNativeFunctions.java),即 isolate 与线程的归属关系保存在线程的 thread-local 数据中。
5. 分离线程:graal_detach_thread
int graal_detach_thread(graal_isolatethread_t* thread);- 将传入的 isolate thread 从它的 isolate 分离,并丢弃与之关联的任何状态或上下文;
- 调用时刻,该 isolate thread 上下文中不得仍有代码在执行;
- 成功返回
0,失败返回非零值。
对应实现先CEntryPointActions.enter(thread)校验上下文,再leaveDetachThread()完成分离(见 CEntryPointNativeFunctions.java)。
6. 拆除 isolate:graal_tear_down_isolate
int graal_tear_down_isolate(graal_isolatethread_t* thread);- 拆除传入的(且仍处于附加状态的)isolate thread 所属的整个 isolate;
- 会等待所有已附加线程先分离,然后丢弃该 isolate 的对象、线程及其他所有关联状态;
- 成功返回
0,失败返回非零值。
阻塞排查提示(源自头文件文档注释):
如果此调用无限期阻塞,说明仍有 Java 线程在收到
Thread.interrupt()事件后没有终止。为避免无限阻塞,应在调用此函数前,于 Java 侧协调这些线程合作式地关闭。要诊断此类问题,可使用选项-R:TearDownWarningSeconds=<secs>检测仍在运行的线程,该选项会打印所有阻塞 tear-down 的线程的堆栈轨迹。
这组说明在源码中以THREAD_TERMINATION_NOTE常量保存并写入头文件(见 CEntryPointNativeFunctions.java),与 C-API.md 中的内容一一对应。
生命周期使用流程(推荐顺序)
将上述函数组合起来,一个典型的 C/C++ 集成流程如下:
- 创建 isolate:调用
graal_create_isolate(NULL, &isolate, &thread)(首参为NULL表示使用默认参数)。创建成功后当前线程即已附加,thread可直接用于后续入口调用; - 调用入口方法:将
thread(或isolate)作为执行上下文传入共享库导出的 entry point; - 多线程场景:其他宿主线程需要访问该 isolate 时,先调用
graal_attach_thread(isolate, &thread)附加;可用graal_get_current_thread(isolate)校验是否已附加; - 线程结束:不再使用后调用
graal_detach_thread(thread)分离; - 整体收尾:所有线程分离、Java 侧工作线程已合作式关闭后,调用
graal_tear_down_isolate(thread)拆除 isolate,释放资源。
顺序要点:
graal_tear_down_isolate会等待附加线程先分离,因此步骤 4 与 5 的顺序至关重要;若 Java 侧启动了非守护性质的长期运行线程,务必在拆除前让其响应中断退出,否则拆除调用可能无限阻塞(可借助-R:TearDownWarningSeconds=<secs>诊断)。
与 JNI Invocation API 的关系
在 C 级 API 之外,还可以使用 JNI Invocation API 从 Java 侧创建 isolate,并暴露、调用内嵌在 native 共享库中的 Java 方法。
二者定位不同:
- Native Image C API:面向从 C/C++ 直接驱动 isolate 的生命周期管理,偏底层、面向宿主语言直接集成;
- JNI Invocation API:走 JNI 标准通道,便于在既有 JNI 生态中复用,从 Java 侧发起创建与调用。
实际项目中可根据宿主代码的技术栈与已有依赖选择合适的通道,二者可互为补充。
从仓库获得的进一步学习资源
围绕 C API 主题,仓库中还提供了以下可直接研读的材料:
- 头文件生成机制:GraalIsolateHeader.java 通过
@CHeader声明与writePreamble将 graal_isolate.preamble 写入生成的头文件; - 全部入口函数实现:CEntryPointNativeFunctions.java 除上述六个函数外,还包含
detach_all_threads_and_tear_down_isolate(分离所有外部启动的附加线程后整体拆除,适用于简化收尾流程的场合); - 完整的 C 集成示例:cinterfacetutorial.c 展示了 C 函数指针回调 Java、结构体/联合体数据传递等完整用法;
- 配套官方指南:
- 构建 Native 共享库指南:讲解如何把 Java 类构建为共享库并生成 C 头文件;
- 与 Native 代码互操作:更广泛的 native 互操作主题;
- JNI Invocation API:Java 侧创建 isolate 的替代通道。
常见问题速查
- 为什么我的头文件里没有这些声明?因为 C API 只在 Native Image以共享库形式构建时生成,且生成内容由
@CHeader(GraalIsolateHeader.class)关联的入口类驱动。请确认构建目标为 shared library。 params可以直接传NULL吗?可以。graal_create_isolate的文档明确说明参数可为NULL,此时使用默认配置创建 isolate。- 线程重复 attach 会怎样?不会出错。
graal_attach_thread对已附加线程幂等,会直接返回已有的 isolate thread 结构。 graal_tear_down_isolate卡住怎么办?按头文件建议:在 Java 侧协调线程响应中断并退出,再用-R:TearDownWarningSeconds=<secs>定位仍在运行的线程及其堆栈。_reserved_4/_reserved_5/auxiliary_image_*/pkey字段怎么用?头文件明确标注为“Internal usage, do not use”,普通集成请将其保持为零值(或默认值),仅依赖已公开语义的字段。
【免费下载链接】graalGraalVM compiles applications into native executables that start instantly, scale fast, and use fewer compute resources 🚀项目地址: https://gitcode.com/gh_mirrors/gr/graal
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考