GraalVM Native Image 运行时模块系统支持(Runtime Module System)深度解析
编译器JIT编译语言运行时高性能计算内存管理【免费下载链接】graalGraalVM compiles applications into native executables that start instantly, scale fast, and use fewer compute resources 项目地址https://gitcode.com/gh_mirrors/gr/graal点击查看免费下载Native Image 在构建可执行镜像时会通过静态分析收集应用可达的类与资源其中就包括对 Java 平台模块系统JPMSjava.lang.Module/java.lang.ModuleLayer的运行时支持。本文以 substratevm/docs/module-system/ModuleSystemSupportRuntime.md 为骨架结合 SubstrateVM 源码深入讲解 Native Image 如何在镜像运行时重建模块图、保证模块感知的访问检查正确、并支持在运行时动态定义Module与ModuleLayer。读完本文你将理解为什么镜像里不能直接复用构建期的模块实例、ModuleLayerFeature的合成流程以及mx hellomodule等测试如何验证这一整套机制。运行时模块系统支持的目标在普通 JVM 上模块系统的状态模块图、reads/opens/exports 关系、各层ModuleLayer由 HotSpot VM 在启动时通过ModuleBootstrap.boot()初始化模块关系的修改如Module.addReads、addExports会同步到 VM 内部的 native 数据结构。而 Native Image 生成的镜像是一个自包含的可执行文件运行时不依赖 HotSpot 的模块子系统因此必须在镜像堆中重建一套完整的模块系统运行时。从源码看com.oracle.svm.core.jdk.ModuleNative见 ModuleNative.java正是用纯 Java 数据结构实现了原先由 VM 完成的 bookkeeping承担了模块查询、关系修改等核心逻辑。整个运行时模块系统支持的目标可以归纳为四点正确的模块实例查询信息运行时Module实例的 name、descriptor、layer、loader 等信息必须与镜像内的真实情况一致正确的模块感知访问检查canRead、isExported、isOpen等检查结果必须与构建期即 JVM 上运行时的语义保持一致并抛出与 JDK 完全相同的异常类型与消息源码注释中明确提到 In order to preserve JCK compatibility, we need to perform all the checks performed by original methods and throw the exact same exception types and messages正确的运行时模块图boot module layer 及各可达的 module layer 必须按分析结果重建动态模块系统支持在运行时通过Module.defineModule/ModuleLayer.defineModulesWithOneLoader等 API 动态定义新的Module与ModuleLayer。Substitutions将 JDK 原生模块方法替换为纯 Java 实现Target_java_lang_Module模块图修改与动态模块定义java.lang.Module中有若干 native 方法如defineModule0、addReads0、addExports0它们在 HotSpot 上会直接操作 VM 内部的模块数据结构。Native Image 通过 Target_java_lang_Module.java 对这些方法进行替换substitution被替换的方法运行时行为说明defineModule0()委托ModuleNative.defineModule(module, isOpen, pns)动态模块支持的关键在运行时创建新模块并注册其包addReads0()委托ModuleNative.addReads(from, to)建立/更新模块间的 reads 关系addExports0()委托ModuleNative.addExports(from, pn, to)限定性导出包addExportsToAll0()委托ModuleNative.addExportsToAll(from, pn)向所有模块导出addExportsToAllUnnamed0()委托ModuleNative.addExportsToAllUnnamed(from, pn)向未命名模块导出isNativeAccessEnabled()无 ForeignPanama支持时直接抛错由ForeignDisabled条件控制同时该 substitution 还对Module的字段做了关键处理descriptor字段通过RecomputeFieldValue(isFinal false, kind None)取消 final 语义原因在于镜像驻留模块的描述符可能因--patch-module而在运行时被更新layer字段同样取消 final其运行时值由ModuleLayerFeatureUtils#patchModuleLayerField在分析结束后通过反射写入。注释明确指出若保持 final分析阶段可能把初始的null常量折叠掉对应 GR-60154特殊标记模块ALL_UNNAMED_MODULE与EVERYONE_MODULE以 alias 形式保留供allows()访问检查使用。Target_java_lang_ModuleLayer替换 boot module layerTarget_java_lang_ModuleLayer.java 中最关键的是boot()的替换Substitute public static ModuleLayer boot() { return RuntimeModuleSupport.singleton().getBootLayer(); }即运行时对ModuleLayer.boot()的调用返回的是由ModuleLayerFeature合成、并存入RuntimeModuleSupport单例的运行时 boot module layer而非任何构建期的 hosted 层。RuntimeModuleSupport见 RuntimeModuleSupport.java是一个 image singleton其bootLayer字段标注为UnknownObjectField值在分析后由 feature 通过setBootLayer设置。此外Target_java_lang_ModuleLayer还对cf、nameToModule字段取消 final 语义使ModuleLayerSubstitutionsSupport.patchBootLayer可以在运行时增强 boot 层例如运行时新增了模块路径时原地重建 Configuration 与 name-to-module 映射同时保留ModuleLayer.boot()的对象身份对CLVClassLoaderValueListModuleLayer使用自定义ModuleLayerCLVTransformer将其替换为运行时单例RuntimeClassLoaderValueSupport.instance().moduleLayerCLV。Target_java_lang_Module_ReflectionData重置 hosted 数据结构构建期的Module实例可能通过反射Module.getDeclaredAnnotations、getAnnotation等携带对 hosted 模块的引用如果这些数据原样进入镜像堆会把构建器的 hosted 模块实例带进运行时。因此 Target_java_lang_Module_ReflectionData.java 对Module.ReflectionData进行替换/重置避免将 hosted 模块拉入镜像文档原文称之为 runtime module synthesizing 的一部分。ModuleLayerFeature合成运行时模块层ModuleLayerFeature见 ModuleLayerFeature.java是一个AutomaticallyRegisteredFeature的InternalFeature负责合成运行时 boot module layer 以及所有在镜像构建期被初始化且可达的 module layer在运行时复刻构建期建立的模块关系reads、opens、exports通过对象替换器object replacer将 hostedModule实例替换为对应的运行时实例。为什么不直接复用构建期的模块与层文档给出了两个核心原因源码也能印证模块图并不相同即使相同也希望能优化——只包含运行时真正需要的模块在不破坏兼容性的前提下裁剪而不是把构建器整个模块图搬进镜像。这一点体现在合成逻辑中只有可达的命名模块 根模块集 额外模块才会进入运行时 boot 层见afterAnalysis中对runtimeImageNamedModules的筛选ModuleLayerFeature.javahosted 模块实例捕获了构建期状态如类加载器、已建立的 reads/opens/exports 集合直接 patch 这些实例及其相关数据结构比合成全新实例更困难、更易出错。因此选择合成路线为每个 hostedModule生成一个全新的运行时Module实例ModuleLayerFeatureUtils#getOrCreateRuntimeModuleForHostedModule通过反射调用Module的构造函数创建ModuleLayerFeature.java并按 loader 分组维护模块名 → 运行时模块的映射。afterAnalysis中的合成流程合成发生在分析完成之后afterAnalysis因为需要利用可达性信息。核心步骤与文档描述一一对应计算运行时根模块集root module setcalculateRootModules(extraModules)是jdk.internal.module.ModuleBootstrap#boot2的自定义版本通过反射复用SystemModuleFinders、ModuleBootstrap.limitFinder、DefaultRoots.compute等方法ModuleLayerFeature.java以保证与 JVM 语义兼容找出所有可达的命名模块遍历分析 universe 中所有可达类型收集其所属模块runtimeImageModules再拆分为命名模块与未命名模块两个集合收集额外模块解析--add-modules显式添加的模块、应用模块路径image module-path所需的系统模块、以及资源Resources.getIncludedResourcesModules()引入的模块并合并ALL-MODULE-PATH处理找出合成模块synthetic modules如jdk.proxy这类为每个类加载器创建的合成模块需要排除为 builder 类加载器创建的那份避免把 hosted 加载器带进镜像ModuleLayerFeature.java找到所有可达的运行时 module layer按与 boot 层的距离排序distanceFromBootModuleLayer以父层先于子层的顺序依次合成——因为合成一个层时其所有父层必须已就绪复刻模块图修改replicateVisibilityModifications将 hosted 层的 reads/opens/exports 关系逐项迁移到运行时模块包括对--add-exports、--add-opens等限定性关系的处理且会特殊处理 builder 模块与所有应用模块之间的关系ModuleLayerFeature.java复刻 native access 信息replicateNativeAccess将--enable-native-access配置与 hosted 层的 native access 状态同步到运行时模块ModuleLayerFeature.java。单个 module layer 的合成synthesizeRuntimeModuleLayer会复用NativeImageClassLoaderSupport中定义的ModuleFindermodulepath finder 与 upgrade/system finder调用Configuration.resolve解析运行时配置再用反射创建一个ModuleLayer实例刻意不用defineModulesWithOneLoader这类公共 API因为它们会顺带创建新的类加载器最后把 name-to-module 映射、parents、servicesCatalog 等字段 patch 进该实例。运行时 boot module layer 与对象替换根模块计算兼容性calculateRootModules复刻ModuleBootstrap#boot2的逻辑保证镜像运行时的默认根模块集与在 JVM 上启动的语义一致boot() 替换ModuleLayer.boot()替换为从RuntimeModuleSupport.singleton().getBootLayer()返回合成层对象替换器duringSetup阶段注册的 object replacer 会在镜像堆扫描时把 hostedModule引用替换为对应运行时模块此外由于Class(DynamicHub).module字段可能不可达还会顺带扫描Class与DynamicHub对象中的模块确保不遗漏ModuleLayerFeature.java。值得注意的边界处理ALL_UNNAMED_MODULE与EVERYONE_MODULE这类特殊标记模块不会被复制而是直接复用同一实例因为它们仅作为访问检查中的哨兵对象所有字段均为 null。分层镜像Layered Images下的特殊处理ModuleLayerFeature同样服务于 Native Image 的分层镜像layered image构建构建共享层时LayerPackagesTransformer会把Module的 opens/exports 包集合注册为未来堆常量future heap constant延迟到应用层再最终计算ModuleLayerFeature.java构建应用层时通过LayeredModuleSingleton继承共享层的包关系并在beforeCompilation中最终确定这些常量模块元数据name-to-module 映射、包集合在封闭可执行镜像中会被压缩为不可变集合shouldCompactModuleMetadata而在分层镜像与 shared library 场景下保持可变以支持跨层 patchModuleLayerFeature.java。测试验证mx hellomodule与 module-graph 测试文档末尾列出了两类测试源码中均可找到对应实现mx hellomodule冒烟测试在 mx_substratevm.py 中定义其流程是用 Maven 构建三个模块hello.lib、hello.app、hello.runtime源码位于 native-image-module-tests先在 JVM 上运行模块测试作为基线-p module-path -m moduletests.hello.app再用native-image从模块构建原生镜像命令行同时带上--add-exportsmoduletests.hello.lib_ü/hello.privateLibmoduletests.hello.app--add-opensmoduletests.hello.lib_ü/hello.private2Libmoduletests.hello.app-p module-path -m moduletests.hello.app这正好覆盖了运行时模块系统对限定性导出/开放、模块路径解析与主模块启动的验证在启用RuntimeClassLoading时还会带运行时模块路径运行镜像验证运行时动态定义 module layersvm.test.expectRuntimeDefinedModuleLayer以及从运行时 JRT 文件系统加载类的能力用-H:AbortOnTypeReachablejavax.xml.namespace.QName断言相关类不是 AOT 可达而是运行时加载启用StrictRuntimeJavaOptions时验证非 Crema非运行时类加载镜像会明确拒绝--module-path等运行时选项。在 CI 中该任务还会组合多种实验选项组合运行见 mx_substratevm.py例如-H:RuntimeClassLoading -H:AllowJRTFileSystem以覆盖动态模块与运行时类加载并存场景。module-graph 测试文档中提到的vm-enterprise/tests/native-image/module-graph企业版仓库专门验证运行时模块图的兼容性与正确性——即运行时Module/ModuleLayer查询结果与 JVM 基线一致。当前社区版仓库中与其功能对等的测试入口就是上文hellomodule任务及com.oracle.svm.test下与模块系统相关的测试类可以结合一起阅读。总结与延伸阅读Native Image 的运行时模块系统支持可以概括为一条主线替换 JDK 原生模块方法Substitutions→ 静态分析收集可达模块ModuleLayerFeature→ 合成运行时模块与模块层RuntimeModuleSupport→ 对象替换保证 hosted 实例不泄漏进镜像。这套机制使得镜像内既保留了 JPMS 的查询与访问检查语义又实现了按需裁剪模块图和运行时动态模块定义能力。若想继续深入建议按以下顺序阅读仓库源码合成主逻辑ModuleLayerFeature.java运行时单例与 boot 层载体RuntimeModuleSupport.java模块方法替换Target_java_lang_Module.java、Target_java_lang_ModuleLayer.java纯 Java 模块数据结构实现ModuleNative.java构建期模块支持builder 模块化运行、--add-exports自动生成等ModuleSystemSupportHosted.md赞分享编译器JIT编译语言运行时高性能计算内存管理【免费下载链接】graalGraalVM compiles applications into native executables that start instantly, scale fast, and use fewer compute resources 项目地址https://gitcode.com/gh_mirrors/gr/graal点击查看免费下载相关推荐PullScrollView集成指南如何将自定义ScrollView应用到现有项目PullScrollView集成指南如何将自定义ScrollView应用到现有项目 想要为你的Android应用添加类似新浪微博和iOS风格的炫酷下拉效果吗编译器JIT编译语言运行时高性能计算内存管理Gson 在 GraalVM Native Image 中的集成测试实践test-graal-native-image 模块深度解析Gson 在 GraalVM Native Image 中的集成测试实践test graal native image 模块深度解析 Gson 是一个将 Ja后端AWS SDK for Java 2.x GraalVM Native Image 实测指南sdk-native-image-test 模块从构建到运行的完整解析AWS SDK for Java 2.x GraalVM Native Image 实测指南sdk native image test 模块从构建到运行的完整后端上一篇Laguna-XS-2.1-3bit社区生态开源贡献和未来路线图展望下一篇Pixelle-Video技术解析企业级AI视频生成引擎的架构与应用实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考