当前位置: 首页 > news >正文

Graalvm配置文件与Feature和Substitute机制介绍

GraalVM介绍

GraalVM提前将Java应用程序编译成独立与机器码二进制文件(可执行文件、动态库文件),如windows系统中的exe文件和dll文件。与在Java虚拟机(JVM)上运行的应用程序相比,这些二进制文件更小,启动速度快100倍,在没有预热的情况下提供峰值性能,并且使用更少的内存和CPU。

下面只介绍开发中与Graalvm相关的配置文件和扩展点(Feature和Substitute机制)以及maven插件配置。

1. 配置文件

1.1 动态代码配置文件

对于动态代码,需要通过指定的json文件来描述其metadata,这样GraalVM才能正确的编译和优化代码。如下类型

  • 反射,配置文件名:reflect-config.json
  • 动态代理, 配置文件名:proxy-config.json
  • 资源文件, 配置文件名:resource-config.json
  • JNI, 配置文件名:jni-config.json,配置被JNI代码调用的java方法,可见jni-lib模块
  • 资源序列化, 配置文件名:serialization-config.json
  • predefined classes, 配置文件名:predefined-classes-config.json

这些JSON文件内容格式可见github: graalvm json file schema

1.2 命令参数配置文件

graalvm推荐开发者为自己的项目提供文件名为native-image.properties的配置文件,在该配置文件中可指定native-image编译分析所需要的配置,在properties文件中,支持如下key:

  1. Args 配置native-image命令参数,如–feature指定扩展的Feature接口、–initialize-at-build-time指定编译时初始化的类。更多命令行选项见Command-line Options
  2. JavaArgs 配置native-image执行时的JVM参数
  3. ImageName 配置最后生成的可执行文件名称

关于native-image.properties的内容更多具体的可见文档Native Image Build Configuration

1.3 存放位置

配置文件都存放在项目jar文件的META-INF/native-image/目录下,native-image会自动加载该目录下的文件。如:

META-INF/
└── native-image└── native-image.properties└── reflect-config.json└── jni-config.json

为了防止多个jar文件内的配置冲突,graalvm推荐在META-INF/native-image/目录下新建以你项目的groupId/artifactID为子目录,如:

META-INF/
└── native-image└── groupID└── artifactID└── native-image.properties└── reflect-config.json└── jni-config.json

2. 动态代码配置示例

2.1 反射

  1. 类所有方法都可以用于反射,reflect-config.json配置如下
[{"name": "com.example.User","allDeclaredConstructors": true,"allPublicMethods": true,"allDeclaredFields": true}
]
  1. 类中指定方法和字段可用于反射,配置示例如下
[{"name": "com.example.User","fields": [{"name": "name"}],"methods": [{"name": "<init>","parameterTypes": []},{"name": "setName","parameterTypes": ["java.lang.String"]}]}
]

2.2 动态代理

  1. 假设接口com.example.IPrintServic会用于动态代理,proxy-config.json配置示例如下
[{"interfaces": ["com.example.IPrintService"]}
]

2.3 资源文件

假设有如下代码用于加载类路径下的资源

try(InputStream stream = this.getClass().getClassLoader().getResourceAsStream(“resource_config.properties”)) {//读取资源内容
} 

resource-config.json配置示例如下

{"resources": {"includes": [{"pattern": "\\Qresource_config.properties\\E"}]}
}

说明: 上面pattern属性值为正则表达式,字符\Q\E之间的内容在正则表达式中表示字面量,即对其中的特殊字符进行原始匹配,比如上面的.不是匹配任意字符,而就是匹配字符串中的.

2.4 类序列化

如果项目中类com.example.User使用了JDK的序列化方式,则其serialization-config.json配置示例如下

[{"name": "com.example.User"}
]

2.5 JNI中调JAVA API

如果JNI代码中有调com.example.Utils类的public static int add(int,int)方法,则jni-config.json配置示例如下

[
{"name":"com.example.Utils","methods":[{"name":"add","parameterTypes":["int","int"] }]
}
]

3. 扩展Feature接口

native-image生成可执行代码过程中会在关键步骤执行用户自定义代码,而用户代码的执行是通过graalvm的Feature机制实现的,开发者可以实现Feature接口来为程序设置一些特定行为,比如通过编码的方式设置以上动态代码配置。

示例

示例需求: 为com.example包下注解了@Reflect注解的类注册反射配置

  1. 实现Feature接口
public class ReflectFeature implements Feature {@Overridepublic void beforeAnalysis(BeforeAnalysisAccess access) {// 注册元数据try (ScanResult graph = new ClassGraph().overrideClassLoaders(access.getApplicationClassLoader()).overrideClasspath(access.getApplicationClassPath()).enableAllInfo().acceptPackages("com.example").scan()) {graph.getClassesWithAnnotation(Reflect.class).forEach(classInfo -> {Class clazz = classInfo.loadClass();RuntimeReflection.register(clazz);RuntimeReflection.registerForReflectiveInstantiation(clazz); // 可通过newInstance()方法创建,与allDeclaredConstructors=true类似RuntimeReflection.register(clazz.getDeclaredFields()); // allDeclaredFields=true类似RuntimeReflection.register(clazz.getDeclaredMethods());// allPublicMethods=true类似});}}
}

注:以上包扫描使用classgraph工具,它与其它反射工具相比优势在于它是直接解析字节码来进行匹配的,好处就是不用把类加载到JVM中。classgraph的maven配置如下:

<dependency><groupId>io.github.classgraph</groupId><artifactId>classgraph</artifactId><version>4.8.173</version>
</dependency>
  1. 注册自定义的com.example.feature.ReflectFeature接口
    有两种方式,
    1. 方式一:在native-image命令行使用参数配置--features=com.example.feature.ReflectFeature指定
    2. 方式二:推荐在native-image.properties文件配置,内容如下
     Args = --features=com.st.graalvm.feature.StepTrackFeature
    

4. Substitute机制

Substitute替换机制使得可以在不修改源代码的前提下,对运行时行为进行适配改造,以保持对原API的兼容。 GraalVM本身也通过该机制对JDK API做了很多兼容性替换。

基于该机制,开发者也可以根据实际需求对一些类、方法、字段甚至是构造函数在native-image编译过程中进行替换。

替换通过注解来实现,native-image编译过程会扫描这些特定注解的类,替换类必须为final类型,命名格式推荐为Target_{原类包名}_{原类名}
替换机制提供的注解如下

  1. @TargetClass 注解替换类,其value值为被替换类

  2. @Substitute 注解替换方法,被注解的方法在方法名和签名上需与目标方法一致

  3. @Alias 注解在替换字段上,被注解的字段在名称和签名上需与目标字段一致,

    1. 搭配**@InjectAccessors**注解可拦截字段的get和set方法,示例如下
     @Alias @InjectAccessors(Target_OriginClass_Version_value.class)
    private static String version;
    static class Target_OriginClass_Version_value {private static String versionValue;static String getVersion() {if (versionValue == null) {versionValue = "44444444L_substitute";}return versionValue;}static void setVersion(String value) {System.out.println("setVersion:"  + value);}
    }
    
    1. 搭配**@RecomputeFieldValue**注解可替换字段的值,示例如下
    // 替换静态字段,需设置isFinal=true
    @Alias @RecomputeFieldValue(kind = Kind.FromAlias, isFinal = true)
    private static boolean useNative = true;
    // 替换字段
    @Alias @RecomputeFieldValue(kind = Kind.FromAlias)
    private boolean inited = true;
    
  4. @Inject 注解在替换类的字段上,该字段在被替换类中不存在,需搭配@RecomputeFieldValue注解,示例如下

@TargetClass(OriginClass.class)
public final class Target_com_example_OriginClass {// 替换并设置字段值@Alias @InjectAccessors(Target_OriginClass_Type_value.class)private TypeEnum type;@Inject @RecomputeFieldValue(kind = Kind.Reset)private TypeEnum typeValue;static class Target_OriginClass_Type_value {static TypeEnum getType(Target_OriginClass receiver) {if (receiver.typeValue == null) {receiver.typeValue = TypeEnum.SUBSTITUTE;}return receiver.typeValue;}static void setType(Target_OriginClass receiver, TypeEnum value) {receiver.typeValue = value;}}
}

5. maven编译打包配置

<plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId><version>0.10.2</version><extensions>true</extensions><executions><execution><id>build-native</id><goals><goal>compile-no-fork</goal></goals><phase>package</phase></execution><execution><id>test-native</id><goals><goal>test</goal></goals><phase>test</phase></execution></executions><configuration><imageName>${project.artifactId}</imageName><sharedLibrary>false</sharedLibrary><metadataRepository><enabled>true</enabled></metadataRepository></configuration>
</plugin>

6. 其它特性

  1. 创建动态库供C/C++,可见文档Build a Native Shared Library
  2. JAVA代码可以直接调动态库接口,不过java代码需要经过native-image编译后才能运行
  3. 创建JNI接口,在之前JNI接口实现都是C/C++编写,使用native-image可以直接使用JAVA来编写JNI接口实现,可见文档JNI Invocation API
  4. 与其它语言集成(包含的语言有JS/Node.js、Python、R、Ruby、WebAssembly、LLVM语言),在Truffle基础上实现,可见文档Embedding Languages

工具

1. 使用native-image-agent代理库生成metadata文件

如果不清楚应用中有哪些动态代码需要提供配置,可以使用graalvm提供native-image-agent来跟踪代码执行情况来生成metadata文件,只需要在java启动参数中加上-agentlib:native-image-agent参数即可,命令示例如下

java -agentlib:native-image-agent=config-output-dir=graalvm -jar graalvm-1.0-SNAPSHOT.jar

总结

  1. Java语言的动态特性阻碍了native-image静态分析和编译,开发者需要提供相应的配置文件native-image才能完整的识别出需要静态编译的代码。
  2. Feature机制和替换机制为开发者提供了在编译期间尽可能多的控制
  3. 通过native-image不仅可以把应用编译为可执行文件,也可以把公共库编译为动态库供其它语言调用,还可以把公共库通过JNI接口暴露给其它java程序使用。

相关文章:

  • 学习测试2-方法
  • JUC并发编程基础(包含线程概念,状态等具体实现)
  • [图解]SysML和EA建模住宅安全系统-10-活动图 入侵者应急响应
  • google 邮件信息收集
  • 使用 Git Hooks 防止敏感信息泄露
  • 维护和管理LDAP之OpenDJ
  • 搜索+动态规划
  • 字节码编程javassist之helloworld
  • Linux CMakeLists编写之可执行程序
  • Linux下编程之内存检查
  • 使用AI工具 Baidu Comate 辅助编码 快速定位修改Bug
  • 数据结构——队列练习题
  • 解决前后端同一个端口跨域问题
  • 如何在Java中实现高性能的网络通信
  • C#中的容器
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • Create React App 使用
  • CSS 三角实现
  • ERLANG 网工修炼笔记 ---- UDP
  • export和import的用法总结
  • go语言学习初探(一)
  • js算法-归并排序(merge_sort)
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • Laravel Mix运行时关于es2015报错解决方案
  • Next.js之基础概念(二)
  • node-glob通配符
  • oldjun 检测网站的经验
  • orm2 中文文档 3.1 模型属性
  • Python十分钟制作属于你自己的个性logo
  • React Native移动开发实战-3-实现页面间的数据传递
  • socket.io+express实现聊天室的思考(三)
  • vue-cli3搭建项目
  • 高性能JavaScript阅读简记(三)
  • 日剧·日综资源集合(建议收藏)
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • ​HTTP与HTTPS:网络通信的安全卫士
  • "无招胜有招"nbsp;史上最全的互…
  • (11)MATLAB PCA+SVM 人脸识别
  • (39)STM32——FLASH闪存
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (顺序)容器的好伴侣 --- 容器适配器
  • (学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解
  • (转)Scala的“=”符号简介
  • *算法训练(leetcode)第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
  • .aanva
  • .mysql secret在哪_MYSQL基本操作(上)
  • .net 8 发布了,试下微软最近强推的MAUI
  • .net wcf memory gates checking failed
  • .Net 中Partitioner static与dynamic的性能对比
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • ::什么意思
  • ??Nginx实现会话保持_Nginx会话保持与Redis的结合_Nginx实现四层负载均衡
  • [2015][note]基于薄向列液晶层的可调谐THz fishnet超材料快速开关——
  • [Android实例] 保持屏幕长亮的两种方法 [转]
  • [AutoSar]状态管理(五)Dcm与BswM、EcuM的复位实现