[超详细] GraalVM打包含有JNI的本地镜像
阅读原文时间:2023年08月19日阅读:1

GraalVM 是一种高性能、多语言通用虚拟机和编译器技术。它由 Oracle 开发并开源,旨在为不同的编程语言和应用场景提供统一的运行时环境和编译器平台。以下是 GraalVM 的一些主要特点和功能:

  1. 多语言支持: GraalVM 支持多种编程语言,包括 Java、JavaScript(Node.js)、Python、Ruby、R、C 和 C++ 等。这使得开发者可以在同一平台上运行不同语言的代码,从而降低了开发和部署的复杂性。
  2. 高性能 JIT 编译器: GraalVM 包含了一款高性能的即时编译器,可以将 Java 代码编译成高效的本地机器码,从而提供更快的执行速度和较低的内存占用。
  3. AOT 编译: GraalVM 还支持 AOT(Ahead-Of-Time)编译,可以将 Java 代码编译成本地可执行文件,无需依赖 JVM。这有助于提高启动速度和减少内存消耗。
  4. 本地图像生成: GraalVM 的本地图像功能可以将 Java 应用程序和依赖项一起打包成本地可执行文件,无需 JVM。这有助于简化部署,并减少应用程序的启动时间和资源占用。
  5. JIT 监控和分析: GraalVM 提供了丰富的监控和分析工具,可以帮助开发者了解 JIT 编译的情况,优化代码性能,以及识别潜在的性能瓶颈。
  6. 多语言互操作性: GraalVM 支持在不同语言之间进行互操作,例如在 Java 代码中调用 JavaScript 函数,或在 JavaScript 代码中调用 Java 类。
  7. 支持 WebAssembly: GraalVM 可以将 Java 代码编译成 WebAssembly 格式,使得 Java 应用程序可以在浏览器中运行。
  8. 开放源代码: GraalVM 是一个开放源代码项目,您可以在 GitHub 上找到它的源代码和文档。

GraalVM 在加速 Java 应用程序、支持多语言开发、优化资源使用等方面提供了一系列创新功能。它被广泛用于构建高性能的应用程序、微服务、嵌入式系统以及各种语言的运行时环境。

注意:

1.1 下载GRaalVM JDK

https://www.graalvm.org/downloads/

1.2 配置GraalVM环境变量

将JAVA_HOME配置到该层目录下

并将JAVA_HOME的/bin添加到PATH中。(不用管这里的jre/bin,我只是偷懒不想换)

打开命令行,输入以下命令,看是否配置环境变量成功

2.1 下载 Visual Studio

https://visualstudio.microsoft.com/zh-hans/

打开Visual Studio Installer,选择使用C++的桌面开发,并如图选择组件。

2.2 配置Visual Studio环境变量

添加以下三个环境变量

INCLUDE=C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\shared;D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.16.27023\include.;

LIB=C:\Program Files (x86)\Windows Kits\10\Lib\10.0.17134.0\um\x64;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.17134.0\ucrt\x64;D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.16.27023\lib\x64;

PATH=%PATH%;D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.16.27023\bin\HostX64\x64

由于GraalVM对于反射的支持不是很好,所以要为JNI的操作类写配置文件。在项目中创建jniconfig,该文件是json格式的文件,用来描述JNI数据交换类的。你的JNI程序是用哪个类交换数据,那你就需要为这个类编写这个配置文件,否则GraalVM的本地镜像不能正常运行。示例如下图,fields是属性,methods是方法,name是路径变量名。

[
  {
    "name" : "com.cInterface.Data",
    "queryAllDeclaredConstructors" : true,
    "queryAllPublicConstructors" : true,
    "queryAllDeclaredMethods" : true,
    "queryAllPublicMethods" : true,
    "allDeclaredClasses" : true,
    "allPublicClasses" : true
  },
  {
    "name" : "com.cInterface.Data",
    "fields" : [
      { "name" : "mParam" },
      { "name" : "mInfo" }
    ],
    "methods" : [
      { "name" : "<init>", "parameterTypes" : [] },
      { "name" : "getParam", "parameterTypes" : [] },
      { "name" : "setInfo", "parameterTypes" : ["byte[]", "int"] }
    ]
  }
]

3.1 编写pom.xml文件

注意:你需要知道你引入的项目依赖是否支持GraalVM,否则可能会出现不可预知的问题。

在pom.xml你主要做的事情是

  • 设置依赖版本(SpringBoot从3.0版本开始支持GraalVM)
  • 指定JDK版本(你下载的GraalVM是什么版本就指定什么java.version)
  • 设置GraalVM打包插件

pom.xml附在文章的最后面

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <configuration>
        <!-- imageName用于设置生成的二进制文件名称 -->
        <imageName>${project.artifactId}</imageName>
        <!-- mainClass用于指定main方法类路径 -->
        <mainClass>com.xxx.Application</mainClass>
        <buildArgs>
            <!-- 这些配置类基本都是指定初始化时间和一些配置,都不用管,并且不要动,否则可能会打包失败 -->
            --no-fallback
            --initialize-at-build-time=org.springframework.util.unit.DataSize
            --initialize-at-build-time=org.slf4j.MDC
            --initialize-at-build-time=ch.qos.logback.classic.Level
            --initialize-at-build-time=ch.qos.logback.classic.Logger
            --initialize-at-build-time=ch.qos.logback.core.util.StatusPrinter
            --initialize-at-build-time=ch.qos.logback.core.status.StatusBase
            --initialize-at-build-time=ch.qos.logback.core.status.InfoStatus
            --initialize-at-build-time=ch.qos.logback.core.spi.AppenderAttachableImpl
            --initialize-at-build-time=org.slf4j.LoggerFactory
            --initialize-at-build-time=ch.qos.logback.core.util.Loader
            --initialize-at-build-time=org.slf4j.impl.StaticLoggerBinder
            --initialize-at-build-time=ch.qos.logback.classic.spi.ThrowableProxy
            --initialize-at-build-time=ch.qos.logback.core.CoreConstants
            --report-unsupported-elements-at-runtime
            --allow-incomplete-classpath
            -H:+ReportExceptionStackTraces
            <!-- 指定刚刚编写的jniconfig路径,我这里比较懒,就直接放在pom.xml相同路径下 -->
            -H:JNIConfigurationFiles=jniconfig
        </buildArgs>
    </configuration>
</plugin>

3.2 打包本地镜像

打开命令行工具,注意是打开这个,不是cmd或者powershell。因为cmd和powershell有命令长度限制,必须使用这个进行命令行操作。

cd到项目的pom.xml文件夹下,输入以下命令进行打包。-Pnative是指定打包成本地镜像,-DskipTests是跳过测试。

mvn -Pnative -DskipTests native:compile

稍等一会,看到以下信息即为打包成功。

3.3 运行本地镜像

打包完成后,项目根路径会有一个target文件夹,里面有个exe文件和awt.dll、java.dll、jwm.dll文件。这就是项目运行的全部文件了。

注意:若你的项目为JNI项目,请把你预先编译好的dll文件放到exe同路径下,或放到java.library.path路径上,否则项目无法正常运行。

GraalVM 的本地镜像打包功能可以将 Java 应用程序及其依赖项打包成本地可执行文件,无需依赖 JVM,从而带来许多好处:

  1. 快速启动: 传统的 Java 应用程序需要加载 JVM 和运行时库,因此启动时间可能较长。而使用 GraalVM 本地镜像,应用程序可以直接运行在本地机器码上,启动时间大幅缩短,适用于需要快速启动的场景,如命令行工具和无服务器应用。
  2. 内存占用减少: 传统的 Java 应用程序需要一定的内存来容纳 JVM 和运行时库。使用 GraalVM 本地镜像,应用程序仅加载所需的代码和依赖项,可以显著减少内存占用,有助于优化资源使用。
  3. 单一分发文件: 本地镜像将应用程序及其所有依赖项打包成一个可执行文件,便于分发和部署。这消除了对于目标系统是否已安装特定版本的 JVM 的依赖,简化了部署过程。
  4. 减少依赖: GraalVM 本地镜像中已经包含了应用程序的依赖项,因此无需手动安装和配置额外的依赖库。
  5. 更轻量级: 本地镜像只包含应用程序和依赖项的精简版本,因此文件大小较小,减少了磁盘空间的使用。
  6. 无需 JVM 安装: 使用 GraalVM 本地镜像运行 Java 应用程序无需安装 JVM,这对于在容器化环境中运行应用程序非常有用,可以减小容器的镜像大小。
  7. 与云原生技术兼容: 本地镜像适用于云原生技术栈,如容器化和无服务器架构,能够更好地满足现代应用开发的需求。

GraalVM 的本地镜像打包功能为 Java 应用程序提供了更快的启动时间、更低的内存占用、更轻量级的分发文件等优势,适用于多种场景,特别是对于需要快速启动和优化资源的应用程序。

pom.xml文件

<?xml version="1.0"?>
<project
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
        xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
        <relativePath />
    </parent>

    <groupId>com.xxx</groupId>
    <artifactId>application</artifactId>
    <version>1.0.1-SNAPSHOT</version>
    <name>Application</name>
    <url>https://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>17</java.version>
    </properties>
    <dependencies>

        <!-- springboot-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.1.2</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <configuration>
                    <!-- imageName用于设置生成的二进制文件名称 -->
                    <imageName>${project.artifactId}</imageName>
                    <!-- mainClass用于指定main方法类路径 -->
                    <mainClass>com.xxx.Application</mainClass>
                    <buildArgs>
                        --no-fallback
                        --initialize-at-build-time=org.springframework.util.unit.DataSize
                        --initialize-at-build-time=org.slf4j.MDC
                        --initialize-at-build-time=ch.qos.logback.classic.Level
                        --initialize-at-build-time=ch.qos.logback.classic.Logger
                        --initialize-at-build-time=ch.qos.logback.core.util.StatusPrinter
                        --initialize-at-build-time=ch.qos.logback.core.status.StatusBase
                        --initialize-at-build-time=ch.qos.logback.core.status.InfoStatus
                        --initialize-at-build-time=ch.qos.logback.core.spi.AppenderAttachableImpl
                        --initialize-at-build-time=org.slf4j.LoggerFactory
                        --initialize-at-build-time=ch.qos.logback.core.util.Loader
                        --initialize-at-build-time=org.slf4j.impl.StaticLoggerBinder
                        --initialize-at-build-time=ch.qos.logback.classic.spi.ThrowableProxy
                        --initialize-at-build-time=ch.qos.logback.core.CoreConstants
                        --report-unsupported-elements-at-runtime
                        --allow-incomplete-classpath
                        -H:+ReportExceptionStackTraces
                        -H:JNIConfigurationFiles=jniconfig
                    </buildArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章