可达性元数据

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

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

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

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

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

要为您的应用程序启用新的用户友好的可达性元数据模式,请在构建时传递选项--exact-reachability-metadata。要仅为具体包启用用户友好的模式,请传递--exact-reachability-metadata=<comma-separated-list-of-packages>

为了概览代码中所有缺少注册的地方,而无需提交精确行为,您可以在启动应用程序时传递-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)。
    • 访问在构建时初始化的静态字段。
    • 访问一个事实上是 final 的变量。
    • 定义一个长度是常量且所有值都是常量的数组。
    • 对其他常量的简单计算(例如,"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 指定元数据的情况。在以下情况下这是必要的:

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

    在以下示例中:

     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 指定元数据 #

所有元数据都指定在reachability-metadata.json 文件中,该文件位于 classpath 上任何META-INF/native-image/<group.id>/<artifactId>/ 的条目中。可达性元数据的 JSON 模式定义在reachability-metadata-schema-v1.1.0.json中。

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

{
  "reflection":[],
  "resources":[],
  "bundles":[],
  "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并且它们存在于 classpath 上,那么该类型也被认为是可达的。

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

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

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

元数据类型 #

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

  • Java 反射java.lang.reflect.* API)允许 Java 代码在运行时检查自己的类、方法、字段及其属性。
  • JNI 允许原生代码在运行时访问类、方法、字段及其属性。
  • 资源允许应用程序中动态访问 classpath 上存在的任意文件。
  • 资源包 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,不应被处理。请注意,即使类型不存在于 classpath 上,上述方法也会抛出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 本机接口 (JNI) #

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 能够访问应用程序 classpath 上或请求代码有权访问的模块路径上的任何资源。资源元数据指示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 不能包含两个连续的 globstar 通配符(例如,"foo/**/**"
    • Glob 不能在与 globstar 通配符相同的级别上包含其他内容(例如,"foo/**bar/x"

给定以下项目结构:

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

您可以:

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

Java 模块中的资源 #

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

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

这将导致native-image工具只包含来自 Java 模块library.moduleresource-file.txt。如果其他模块或 classpath 包含与模式resource-file.txt匹配的资源,则只有library-module中的资源会被注册以包含在原生可执行文件中。Native Image 还将确保模块在运行时能够被访问。

考虑以下代码模式:

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

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

嵌入式资源信息 #

有两种方法可以查看原生可执行文件中包含了哪些资源:

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

对于每个注册的资源,您将获得:

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

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

资源包 #

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

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

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

要从特定模块请求一个 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, )的调用,当所有参数和接收器都是常量时,原生镜像会检测到对这些函数的调用。例如,以下调用将注册SerializlableClass用于序列化:

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

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

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

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

JSON 中的序列化元数据 #

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

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

{
  "reflection": [
    {
      "type": "serialized.Type",
      "serializable": true
    }
  ]
}

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

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

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

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

指定的constructorToCall与在cl的常规序列化期间自动使用的构造函数不同。当一个类注册用于运行时序列化时,所有潜在的自定义构造函数都会自动注册。因此,此用例不需要任何额外的元数据。

可达性元数据示例 #

以下是您可以在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,
      "serializable": 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"
    }
  ]
}

在运行时定义类 #

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

  1. 修改或重新配置您的应用程序(或第三方库),使其不在运行时生成类或通过非内置类加载器加载它们。
  2. 如果必须生成类,请尝试在专用类的静态初始化器中在构建时生成它们。生成的java.lang.Class对象应存储在静态字段中,并通过将--initialize-at-build-time=<class_name>作为构建参数来初始化专用类。
  3. 如果以上都不适用,请使用Native Image Agent运行应用程序,并使用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"
      }
    ]
  }
]

注意:预定义类元数据不应手动编写。注意:预定义类是针对遗留项目的最佳尝试方法,它们不保证能正常工作。

延伸阅读 #

联系我们