可达性元数据

JVM 的动态语言功能(例如,反射和资源处理)在运行时计算动态访问的程序元素,例如字段、方法或资源 URL。在 HotSpot 中,这是可能的,因为所有类文件和资源都在运行时可用,并且可以由运行时加载。所有类和资源的可用性以及它们在运行时的加载会带来内存和启动时间的额外开销。

为了使原生二进制文件变小,native-image 构建器在构建时执行静态分析,以确定应用程序正确性所需的必要程序元素。较小的二进制文件可以实现快速的应用程序启动和较低的内存占用率,但是它们也有一些代价:通过静态分析来确定动态访问的应用程序元素是不可行的,因为这些元素的可达性取决于仅在运行时才可用的数据。

为了确保将必要的动态访问元素包含到原生二进制文件中,native-image 构建器需要可达性元数据(以下简称元数据)。为构建器提供正确且完整的可达性元数据可以保证应用程序的正确性,并确保在运行时与第三方库的兼容性。

可以通过以下方式将元数据提供给 native-image 构建器

注意:Native Image 正在迁移到更用户友好的可达性元数据实现,该实现可以及早发现问题并允许轻松调试。

要为应用程序启用新的用户友好可达性元数据模式,请在构建时传递选项 --exact-reachability-metadata。要仅为具体包启用用户友好模式,请传递 --exact-reachability-metadata=<以逗号分隔的包列表>

要概述代码中所有发生缺失注册的地方,而不必提交到确切的行为,可以在启动应用程序时传递 -XX:MissingRegistrationReportingMode=Warn

要检测应用程序意外忽略缺失注册错误(使用 catch (Throwable t) 块)的地方,可以在启动应用程序时传递 -XX:MissingRegistrationReportingMode=Exit。然后,应用程序将无条件地打印包含堆栈跟踪的错误消息并立即退出。这种行为非常适合运行应用程序测试以保证所有元数据都被包含。

反射的用户友好实现将在未来的 GraalVM 版本中成为默认值,因此及时采用这一点非常重要,以避免项目中断。

目录 #

在代码中计算元数据 #

可以通过两种方式在代码中计算元数据

  1. 通过向动态访问 JVM 元素的函数提供常量参数。例如,请参见以下代码中的 Class#forName

     class ReflectiveAccess {
         public Class<Foo> fetchFoo() throws ClassNotFoundException {
             return Class.forName("Foo");
         }
     }
    

    此处,Class.forName("Foo") 在构建时被评估为常量。在构建原生二进制文件时,该值将存储到其初始堆中。如果类 Foo 不存在,则对 Class#forName 的调用将被转换为 throw ClassNotFoundException("Foo")

    常量定义为

    • 文字(例如,"Foo"1)。
    • 对在构建时初始化的静态字段的访问。
    • 对有效最终变量的访问。
    • 定义一个长度为常量且所有值为常量的数组。
    • 对其他常量的简单计算(例如,"F" + "oo",或对数组的索引)。

    在传递常量数组时,以下声明和填充数组的方法从 native-image 构建器的角度来看是等效的

      Class<?>[] params0 = new Class<?>[]{String.class, int.class};
      Integer.class.getMethod("parseInt", params0);
    
      Class<?>[] params1 = new Class<?>[2];
      params1[0] = Class.forName("java.lang.String");
      params1[1] = int.class;
      Integer.class.getMethod("parseInt", params1);
    
      Class<?>[] params2 = {String.class, int.class};
      Integer.class.getMethod("parseInt", params2);
    

    请注意,Native Image 当前会积极地计算常量,因此无法精确地指定在构建时什么是常量。

  2. 通过在构建时初始化类并将动态访问的元素存储到原生可执行文件的初始堆中。这种提供元数据的方式适用于无法使用常量或 JSON 指定元数据的情况。这在以下情况下是必要的:

    • 用户代码需要生成新的类字节码。
    • 用户代码需要遍历类路径来计算应用程序所需的动态访问程序元素。

    在以下示例中

     class InitializedAtBuildTime {
         private static Class<?> aClass;
         static {
             try {
                 aClass = Class.forName(readFile("class.txt"));
             } catch (ClassNotFoundException e) {
                 throw RuntimeException(e);
             }
         }
    
         public Class<?> fetchFoo() {
             return aClass;
         }
     }
    

仅当该部分堆可通过封闭方法(例如,InitializedAtBuildTime#fetchFoo)或静态字段(例如,InitializedAtBuildTime.aClass)到达时,动态访问的元素才会被包含到原生可执行文件的堆中。

使用 JSON 指定元数据 #

所有在类路径上的任何条目中的META-INF/native-image/<groupId>/<artifactId>/中找到的reachability-metadata.json 文件中指定的元数据。可达性元数据的 JSON 模式定义在reachability-metadata-schema-v1.0.0.json中。

可以在示例部分中找到一个示例reachability-metadata.json 文件。reachability-metadata.json 配置包含一个包含每个元数据类型的单个字段的对象。顶级对象中的每个字段都包含一个元数据条目数组

{
  "reflection":[],
  "resources":[],
  "bundles":[],
  "serialization":[],
  "jni":[]
}

例如,Java 反射元数据在 reflection 下指定,示例条目如下所示

{
  "reflection": [
    {
      "type": "Foo"
    }
  ]
}

条件元数据条目 #

基于 JSON 的元数据中的每个条目都应该是条件的,以避免原生二进制文件大小不必要地增加。条件条目通过以以下方式向条目添加 condition 字段来指定

{
  "condition": {
    "typeReached": "<fully-qualified-class-name>"
  },
  <metadata-entry>
}

只有当在运行时指定了完全限定类型的到达时,具有 typeReached 条件的元数据条目才被认为在运行时可用。在此之前,对 metadata-entry 表示的元素的所有动态访问将表现得好像 metadata-entry 不存在一样。这意味着这些动态访问将抛出缺失注册错误。

类初始化例程为该类型(类或接口)启动之前,或该类型的任何子类型被到达时,类型在运行时被到达。对于以下示例中保护元数据条目的 "typeReached": "ConditionType",类型被认为是到达的

class SuperType {
    static {
        // ConditionType reached (subtype reached) => metadata entry available
    }
}
class ConditionType extends SuperType {
    static {
        // ConditionType reached (before static initializer) => metadata entry available
    }
    static ConditionType singleton() {
        // ConditionType reached (already initialized) => metadata entry available
    }
}
public class App {
    public static void main(String[] args) {
        // ConditionType not reached => metadata entry not available
        ConditionType.class;
        // ConditionType not reached (ConditionType.class doesn't start class initialization) => metadata entry not available  
        ConditionType.singleton();
        // ConditionType reached (already initialized) => metadata entry available
    }
}

如果类型被标记为 initialize-at-build-time 或其任何子类型被标记为 initialize-at-build-time 且它们存在于类路径上,那么类型也会被到达。

数组类型永远不会被标记为到达,因此不能在条件中使用。

当在构建时完全限定类型可达时,条件元数据条目将被包含到映像中。此条目会影响映像大小,并且只有在运行时条件到达时,它才会在运行时可用。

您可以在GraalVM 可达性元数据存储库中找到更多元数据文件的示例。

元数据类型 #

Native Image 接受以下类型的可达性元数据

  • Java 反射java.lang.reflect.* API)使 Java 代码能够在运行时检查自己的类、方法、字段及其属性。
  • JNI 允许原生代码在运行时访问类、方法、字段及其属性。
  • 资源 允许在应用程序中动态访问类路径上存在的任意文件。
  • 资源包 Java 本地化支持(java.util.ResourceBundle),使 Java 代码能够加载 L10N 资源。
  • 序列化 使得能够将 Java 对象写入(和读取)到流(和从流)。
  • (实验性)预定义类 提供对动态生成的类的支持。

反射 #

对于本节中的所有方法,Native Image 将在构建时计算可达性,前提是所有调用参数都是常量。在代码中提供常量参数是提供元数据的首选方式,因为它不需要在外部 JSON 文件中复制信息。

Java 中的反射以 java.lang.Class 开头,它允许获取更深层的反射元素,例如方法和字段。可以通过 java.lang.Class 上的以下静态函数以反射的方式获取类

  • java.lang.Class forName(java.lang.String) throws java.lang.ClassNotFoundException
  • java.lang.Class forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException
  • java.lang.Class forName(java.lang.Module, java.lang.String)
  • java.lang.Class arrayType() - 需要数组类型的元数据。也可以通过使用 java.lang.ClassLoader#loadClass(String) 从名称加载类来以反射的方式获取类。

要为以反射方式获取 Class 的调用提供元数据,必须将以下条目添加到 reachability-metadata.json 中的 reflection 数组中

{
  "type": "FullyQualifiedReflectivelyAccessedType"
}

对于代理类,java.lang.Class 是使用 java.lang.reflect.Proxy 上的以下方法获取的

  • java.lang.Class getProxyClass(java.lang.ClassLoader, java.lang.Class[]) throws java.lang.IllegalArgumentException
  • java.lang.Object newProxyInstance(java.lang.ClassLoader, java.lang.Class[], java.lang.reflect.InvocationHandler)

代理类的元数据以有序的接口集合形式存在,该集合定义了代理。

{
  "type": {
    "proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"]
  }
}

在没有提供元数据的情况下调用上述方法将导致抛出MissingReflectionRegistrationError,它扩展了java.lang.Error,不应该被处理。 请注意,即使类路径上不存在类型,上述方法也会抛出MissingReflectionRegistrationError

如果未为给定类型提供元数据,则java.lang.Class上的以下方法将抛出MissingRegistrationError

  • Constructor getConstructor(Class[]) throws NoSuchMethodException,SecurityException
  • Constructor getDeclaredConstructor(Class[]) throws NoSuchMethodException,SecurityException
  • Constructor[] getConstructors() throws SecurityException
  • Constructor[] getDeclaredConstructors() throws SecurityException
  • Method getMethod(String,Class[]) throws NoSuchMethodException,SecurityException
  • Method getDeclaredMethod(String,Class[]) throws NoSuchMethodException,SecurityException
  • Method[] getMethods() throws SecurityException
  • Method[] getDeclaredMethods() throws SecurityException
  • Field getField(String) throws NoSuchFieldException,SecurityException
  • Field getDeclaredField(String) throws NoSuchFieldException,SecurityException
  • Field[] getFields() throws SecurityException
  • Field[] getDeclaredFields() throws SecurityException
  • RecordComponent[] getRecordComponents()
  • Class[] getPermittedSubclasses()
  • Object[] getSigners()
  • Class[] getNestMembers()
  • Class[] getClasses()
  • Class[] getDeclaredClasses() throws SecurityException

此外,通过java.lang.invoke.MethodHandles.Lookup进行的所有反射查找也需要类型元数据存在,否则它们将抛出MissingReflectionRegistrationError

请注意,对于lambda代理类,无法提供元数据。 这是一个已知问题,将在GraalVM的未来版本中解决。

反射方法调用 #

要反射地调用方法,必须将方法签名添加到type元数据中。

{
  "type": "TypeWhoseMethodsAreInvoked",
  "methods": [
    {"name": "<methodName1>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]},
    {"name": "<methodName2>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]}
  ]
}

为了方便起见,可以通过在reachability-metadata.json中添加以下内容来允许对一组方法进行方法调用。

{
  "type": "TypeWhoseMethodsAreInvoked",
  "allDeclaredConstructors": true,
  "allPublicConstructors": true,
  "allDeclaredMethods": true,
  "allPublicMethods": true
}

allDeclaredConstructorsallDeclaredMethods 允许调用在给定类型上声明的方法。 allPublicConstructorsallPublicMethods 允许调用在给定类型及其所有超类型上定义的所有公共方法。

如果缺少方法调用元数据,以下方法将抛出MissingReflectionRegistrationError

  • java.lang.reflect.Method#invoke(Object, Object...)
  • java.lang.reflect.Constructor#newInstance(Object...)
  • java.lang.invoke.MethodHandle#invokeExact(Object...)
  • java.lang.invoke.MethodHandle#invokeWithArguments(所有重载版本)

反射字段值访问 #

要反射地访问(获取或设置)字段值,必须将有关字段名称的元数据添加到类型中。

{
  "type": "TypeWhoseFieldValuesAreAccessed",
  "fields": [{"name": "<fieldName1>"}, {"name": "<fieldNameI>"}, {"name": "<fieldNameN>"}]
}

为了方便起见,可以通过在reachability-metadata.json中添加以下内容来允许对所有字段进行字段值访问。

{
  "type": "TypeWhoseFieldValuesAreAccessed",
  "allDeclaredFields": true,
  "allPublicFields": true
}

allDeclaredFields 允许访问在给定类型上声明的所有字段,而 allPublicFields 允许访问给定类型及其所有超类型的所有公共字段。

如果缺少字段值访问元数据,以下方法将抛出MissingReflectionRegistrationError

  • java.lang.reflect.Field#get(Object)
  • java.lang.reflect.Field#set(Object, Object)
  • java.lang.reflect.VarHandle 上的所有访问器方法。

类型的不安全分配 #

对于通过sun.misc.Unsafe#allocateInstance(Class<?>) 或通过AllocObject(jClass) 从本地代码进行类型的不安全分配,我们必须提供以下元数据。

{
  "type": "FullyQualifiedUnsafeAllocatedType",
  "unsafeAllocated": true
}

否则,这些方法将抛出MissingReflectionRegistrationError

反射元数据摘要 #

JSON 中类型的总体定义可以具有以下值

{
  "condition": {
    "typeReached": "<condition-class>"
  },
  "type": "<class>|<proxy-interface-list>",
  "fields": [
    {"name": "<fieldName>"}
  ],
  "methods": [
    {"name": "<methodName>", "parameterTypes": ["<param-type>"]}
  ],
  "allDeclaredConstructors": true,
  "allPublicConstructors": true,
  "allDeclaredMethods": true,
  "allPublicMethods": true,
  "allDeclaredFields": true,
  "allPublicFields": true,
  "unsafeAllocated": true
}

Java 本地接口 #

Java 本地接口 (JNI) 允许本地代码访问任意 Java 类型和类型成员。 Native Image 无法预测此类本地代码将查找、写入或调用什么。 要为使用 JNI 访问 Java 值的 Java 应用程序构建本地二进制文件,需要 JNI 元数据。

例如,以下C代码

jclass clazz = FindClass(env, "jni/accessed/Type");

查找jni.accessed.Type 类,然后可以用来实例化jni.accessed.Type,调用其方法或访问其字段。

上述调用的元数据条目只能通过reachability-metadata.json 提供。 在jni 字段中指定type 条目

{
  "jni":[
    {
      "type": "jni.accessed.Type"
    }
  ]
}

添加类型元数据并不允许使用GetFieldIDGetStaticFieldIDGetStaticMethodIDGetMethodID 获取其所有字段和方法。

要访问字段值,我们需要提供字段名称

{
  "type": "jni.accessed.Type",
  "fields": [{"name": "value"}]
}

要访问所有字段,可以使用以下属性

{
  "type": "jni.accessed.Type",
  "allDeclaredFields": true,
  "allPublicFields": true
}

allDeclaredFields 允许访问在给定类型上声明的所有字段,而 allPublicFields 允许访问给定类型及其所有超类型的所有公共字段。

要从 JNI 调用 Java 方法,我们必须提供方法签名的元数据

{
  "type": "jni.accessed.Type",
  "methods": [
    {"name": "<methodName1>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]},
    {"name": "<methodName2>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]}
  ]
}

为了方便起见,可以通过添加以下内容来允许对一组方法进行方法调用。

{
  "type": "jni.accessed.Type",
  "allDeclaredConstructors": true,
  "allPublicConstructors": true,
  "allDeclaredMethods": true,
  "allPublicMethods": true
}

allDeclaredConstructorsallDeclaredMethods 允许调用在给定类型上声明的方法。 allPublicConstructorsallPublicMethods 允许调用在给定类型及其所有超类型上定义的所有公共方法。

要使用AllocObject 分配类型对象,元数据必须存储在reflection 部分

{
  "reflection": [
    {
      "type": "jni.accessed.Type",
      "unsafeAllocated": true
    }
  ]
}

未能为从本地代码动态访问的元素提供元数据将导致异常(MissingJNIRegistrationError)。

请注意,大多数使用 JNI 的库没有正确处理异常,因此要查看哪些元素丢失,必须使用--exact-reachability-metadata-XX:MissingRegistrationReportingMode=Warn 结合使用。

资源 #

Java 能够访问应用程序类路径上的任何资源,或者请求代码具有访问权限的模块路径上的任何资源。 资源元数据指示native-image 构建器将指定的资源和资源包包含在生成的二进制文件中。 这种方法的结果是,使用资源进行配置(例如日志记录)的应用程序的某些部分在构建时实际上会进行配置。

Native Image 将检测对java.lang.Class#getResourcejava.lang.Class#getResourceAsStream 的调用,其中

  • 调用这些方法的类是常量。
  • 第一个参数(name)是常量,并自动注册这些资源。

以下代码将开箱即用,因为

  • 它使用类字面量(Example.class)作为接收器。
  • 它使用字符串字面量作为name 参数。
    class Example {
     public void conquerTheWorld() {
         InputStream plan = Example.class.getResourceAsStream("plans/v2/conquer_the_world.txt");
     }
    }
    

JSON 中的资源元数据 #

资源元数据在reachability-metadata.json 文件的resources 字段中指定。 以下是如何使用资源元数据的示例。

{
  "resources": [
    {
      "glob": "path1/level*/**"
    }
  ]
}

glob 字段使用glob 模式 规则的子集来指定资源。 指定资源路径时,有几个规则需要遵守。

  • native-image 工具仅支持星号*)和通配符**)通配符模式。
    • 根据定义,星号 可以匹配一个级别上的任意数量的任意字符,而通配符 可以匹配任意级别上的任意数量的字符。
    • 如果需要将星号按字面意义对待(没有特殊含义),可以使用\ 对其进行转义(例如,\*)。
  • 在 glob 中,级别 表示用/ 分隔的模式的一部分。
  • 编写 glob 模式时,必须遵守以下规则
    • Glob 不能为空(例如,"")。
    • Glob 不能以尾部斜杠(/)结尾(例如,"foo/bar/")。
    • Glob 在一个级别上不能包含两个以上的连续(未转义的)* 字符(例如,"foo/***/")。
    • Glob 不能包含空级别(例如,"foo//bar")。
    • Glob 不能包含两个连续的通配符(例如,"foo/**/**")。
    • Glob 在与通配符相同的级别上不能有其他内容(例如,"foo/**bar/x")。

假设有以下项目结构

app-root
└── src
    └── main
        └── resources
            ├── Resource0.txt
            └── Resource1.txt

您可以

  • 使用 glob **/Resource*.txt 包含所有资源({ "glob":})。
  • 使用 glob **/Resource0.txt 包含Resource0.txt
  • 使用 glob **/Resource0.txt**/Resource1.txt 包含Resource0.txtResource1.txt

Java 模块中的资源 #

对于每个资源或资源包,都可以指定应从中获取资源或资源包的模块。 可以在每个条目的单独的module 字段中指定模块名称。 例如

{
   "resources": [
      {
        "module:": "library.module",
        "glob": "resource-file.txt" 
      }
   ]
}

这将导致native-image 工具仅包含来自 Java 模块library.moduleresource-file.txt。 如果其他模块或包含与模式resource-file.txt 匹配的资源的类路径,则只注册library-module 中的资源包含到本地可执行文件中。 Native Image 还将确保在运行时保证可以访问这些模块。

请参考以下代码模式

InputStream resource = ModuleLayer.boot().findModule("library.module").getResourceAsStream(resourcePath);

对于上面描述的已注册资源,它将始终按预期工作(即使模块不包含任何被静态分析认为可到达的代码)。

嵌入式资源信息 #

有两种方法可以查看哪些资源已包含在本地可执行文件中

  1. 使用选项--emit build-report 为本地可执行文件生成构建报告。 在那里,您可以在“资源”选项卡下找到有关所有包含资源的信息。
  2. 使用选项-H:+GenerateEmbeddedResourcesFile 生成一个 JSON 文件embedded-resources.json,其中列出了所有包含的资源。

对于每个注册的资源,您都会获得

  • 模块(如果资源不属于任何模块,则为unnamed)。
  • 名称(资源路径)。
  • 来源(系统上资源的位置)。
  • 类型(资源是文件、目录还是丢失)。
  • 大小(实际资源大小)。

注意:资源目录的大小仅表示所有目录条目的名称的大小(不是内容大小的总和)。

资源包 #

Java 本地化支持(java.util.ResourceBundle)能够加载 L10N 资源并显示针对特定区域设置 本地化的消息。 Native Image 需要了解应用程序使用的资源包,以便它可以将适当的资源和程序元素包含到应用程序中。

可以在reachability-metadata.jsonbundles 部分中指定一个简单的包

{
  "bundles": [
    {
      "name":"your.pkg.Bundle"
    }
  ]
}

要从特定模块请求包

{
  "bundles": [
    {
      "name":"app.module:module.pkg.Bundle"
    }
  ]
}

默认情况下,资源包包含在包含在映像中 的所有区域设置中。 以下是如何为包包含特定区域设置的示例。

{
  "bundles": [
    {
      "name": "specific.locales.Bundle",
      "locales": ["en", "de", "sk"]
    }
  ]
}

区域设置 #

还可以指定哪些区域设置应包含在本地可执行文件中,以及哪个应该是默认区域设置。 例如,要将默认区域设置切换到瑞士德语,并同时包含法语和英语,请使用以下选项

native-image -Duser.country=CH -Duser.language=de -H:IncludeLocales=fr,en

区域设置使用语言标记 指定。 您可以通过-H:+IncludeAllLocales 包含所有区域设置,但请注意,这会增加生成的可执行文件的大小。

序列化 #

Java 可以序列化(或反序列化)任何实现Serializable 接口的类。 Native Image 支持使用正确的序列化元数据注册进行序列化(或反序列化)。 这是必要的,因为序列化通常需要对要序列化的对象进行反射访问。

代码中的序列化元数据注册 #

Native Image 检测对 ObjectInputFilter.Config#createFilter(String pattern) 的调用,如果 pattern 参数是常量,则会在模式中提到的所有类都会被注册以供序列化。例如,以下模式将注册类 pkg.SerializableClass 以供序列化

  var filter = ObjectInputFilter.Config.createFilter("pkg.SerializableClass;!*;")
  objectInputStream.setObjectInputFilter(proof);

使用此模式对 JVM 的安全性具有积极的副作用,因为只有 pkg.SerializableClass 可以被 objectInputStream 接收。

通配符模式仅对封闭类的 Lambda 代理类执行序列化注册。例如,要注册封闭类 pkg.LambdaHolder 中的 Lambda 序列化,请使用

  ObjectInputFilter.Config.createFilter("pkg.LambdaHolder$$Lambda*;")

"pkg.**""pkg.Prefix*" 这样的模式不会执行序列化注册,因为它们过于通用,会显著增加镜像大小。

对于对 sun.reflect.ReflectionFactory#newConstructorForSerialization(java.lang.Class)sun.reflect.ReflectionFactory#newConstructorForSerialization(java.lang.Class, ) 的调用,Native Image 会在所有参数和接收者都是常量时检测到对这些函数的调用。例如,以下调用将注册 SerializlableClass 以供序列化

  ReflectionFactory.getReflectionFactory().newConstructorForSerialization(SerializableClass.class);

要创建用于序列化的自定义构造函数,请使用

  var constructor = SuperSuperClass.class.getDeclaredConstructor();
  var newConstructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(BaseClass.class, constructor);

代理类只能通过 JSON 文件注册以供序列化。

JSON 中的序列化元数据 #

序列化元数据在 reachability-metadata.jsonserialization 部分中指定。

要指定常规 serialized.Type,请使用

{
  "serialization": [
    {
      "type": "serialized.Type"
    }
  ]
}

要指定用于序列化的代理类,请使用以下条目

{
  "serialization": [
    {
      "type": {
        "proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"]
      }
    }
  ]
}

在极少数情况下,应用程序可能会显式调用

    ReflectionFactory.newConstructorForSerialization(Class<?> cl, Constructor<?> constructorToCall);

其中传递的 constructorToCallcl 的常规序列化将自动使用的不同。

要支持此类序列化用例,还可以注册具有自定义 constructorToCall 的类的序列化。例如,要允许 org.apache.spark.SparkContext$$anonfun$hadoopFile$1 的序列化,请使用 java.lang.Object 的声明构造函数作为自定义 targetConstructor,请使用

{
  "serialization": [
    {
      "type": "<fully-qualified-class-name>",
      "customTargetConstructorClass": "<custom-target-constructor-class>"
    }
  ]
}

示例可达性元数据 #

以下是您可以用在 reachabilty-metadata.json 中的示例可达性元数据配置

{
  "reflection": [
    {
      "type": "reflectively.accessed.Type",
      "fields": [
        {
          "name": "field1"
        }
      ],
      "methods": [
        {
          "name": "method1",
          "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"] 
        }
      ],
      "allDeclaredConstructors": true,
      "allPublicConstructors": true,
      "allDeclaredFields": true,
      "allPublicFields": true,
      "allDeclaredMethods": true,
      "allPublicMethods": true,
      "unsafeAllocated": true
    }
  ],
  "jni": [
    {
      "type": "jni.accessed.Type",
      "fields": [
        {
          "name": "field1"
        }
      ],
      "methods": [
        {
          "name": "method1",
          "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]
        }
      ],
      "allDeclaredConstructors": true,
      "allPublicConstructors": true,
      "allDeclaredFields": true,
      "allPublicFields": true,
      "allDeclaredMethods": true,
      "allPublicMethods": true
    }
  ],
  "resources": [
    {
      "module": "optional.module.of.a.resource",
      "glob": "path1/level*/**"
    }
  ],
  "bundles": [
    {
      "name": "fully.qualified.bundle.name",
      "locales": ["en", "de", "other_optional_locales"]
    }
  ],
  "serialization": [
    {
      "type": "serialized.Type",
      "customTargetConstructorClass": "optional.serialized.super.Type"
    }
  ]
}

在运行时定义类 #

Java 支持在运行时从字节码加载新类,这在 Native Image 中是不可能的,因为所有类都必须在构建时已知(“封闭世界假设”)。要解决此问题,有以下选项

  1. 修改或重新配置您的应用程序(或第三方库),使其不会在运行时生成类或通过非内置类加载器加载它们。
  2. 如果必须生成类,请尝试在专用类的静态初始化器中在构建时生成它们。生成的 java.lang.Class 对象应存储在静态字段中,并通过传递 --initialize-at-build-time=<class_name> 作为构建参数来初始化专用类。
  3. 如果上述方法都不适用,请使用 Native Image 代理 运行应用程序并使用 java -agentlib:native-image-agent=config-output-dir=<config-dir>,experimental-class-define-support <application-arguments> 收集预定义类。在运行时,如果尝试加载与跟踪期间遇到的类之一具有相同名称和字节码的类,则将向应用程序提供预定义类。

预定义类元数据在 predefined-classes-config.json 文件中指定,并符合 predefined-classes-config-schema-v1.0.0.json 中定义的 JSON 模式。该模式还包括有关此配置工作原理的更多详细信息和解释。以下是 predefined-classes-config.json 的示例

[
  {
    "type": "agent-extracted",
    "classes": [
      {
        "hash": "<class-bytecodes-hash>",
        "nameInfo": "<class-name"
      }
    ]
  }
]

注意:预定义类元数据不应手动编写。注意:预定义类是针对遗留项目的最佳努力方法,不能保证它们有效。

进一步阅读 #

联系我们