• GraalVM(云原生时代的Java)和IoT在边缘侧落地与实践


    环顾四周,皆是对手!

    • 云时代的掉队者,由于Java启动的高延时、对资源的高占用、导致在Serverless及FaaS架构下力不从心,在越来越流行的边缘计算、IoT方向上也是难觅踪影;
    • Java语言在业务服务开发中孤独求败,但在系统级应用领域几乎是C、C++、搅局者Go、黑天鹅Rust的天下;
    • 移动应用、敏捷应用的追随者,移动应用中Android逐步去Java,前端又是JS的世界,敏捷开发方面前有Ruby、Python后有NodeJS;

    多语言比拼.png

    此时众多的Javaer会不经意发问:学Java还有未来么?

    你可以嫌弃Java, 但是可以永远相信JVM! 在云原生如日中天、Serverless日渐成熟、新语言百花齐放的当下,跨语言、Native支持、高性能低资源占用的技术必定是其璀璨的明珠,而GraalVM正是这样一个承载了JVM未来,将Java带入下一波技术的弄潮儿。

    GraalVM - 云原生时代的Java

    “一次编写,到处运行“是Java语言的特性,这一重要特性依靠的是JVM虚拟机

    在讨论GraalVM之前,我们先聊下Java代码是怎么运行的?

    从硬件视角来看,Java字节码无法直接执行。因此,Java虚拟机需要将字节码翻译成机器码。

    代码如何在JVM运行?.jpg

    从虚拟机视角来看,执行Java代码首先需要将它编译而成的 class 文件加载到 Java 虚拟机中。加载后的 Java 类会被存放于方法区(Method Area)中。实际运行时,虚拟机会执行方法区内的代码。Java 虚拟机同样也在内存中划分出堆和栈来存储运行时数据。

    不同的是,Java 虚拟机会将栈细分为面向 Java 方法的 Java 方法栈,面向本地方法(用 C++ 写的 native 方法)的本地方法栈,以及存放各个线程执行位置的 PC 寄存器。

    Java虚拟机-软件层面.png

    在运行过程中,每当调用进入一个 Java 方法,Java 虚拟机会在当前线程的 Java 方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。这个栈帧的大小是提前计算好的,而且 Java 虚拟机不要求栈帧在内存空间里连续分布。

    当退出当前执行的方法时,不管是正常返回还是异常返回,Java 虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。

    GraalVM带来哪些神奇的黑魔法🧙‍♂️

    1. 更快、更轻量化的应用

      GraalVM的高性能JIT编译器可以生成优化的本地机器代码,由于采用了先进的编译器优化和积极复杂的内联技术,运行速度更快,产生的垃圾更少,使用的CPU更少。最终的结果是应用程序运行速度更快,消耗的资源更少,从而降低了云和基础设施的成本。

    2. Ahead-Of-Time(AOT)提前编译技术

      AOT 提前编译,是相对于即时编译而言的。AOT在运行过程中耗费 CPU 资源来进行即时编译,而程序也能够在启动的瞬间就达到理想的性能。例如 C 和 C++语言采用的是AOT静态编译,直接将代码转换成机器码执行。而 Java 一直采用的是解释 + 即时编译技术。

      GraalVM 的 AOT 编译实际上是借助了 SubstrateVM 编译框架,可以将 SubstrateVM 理解为一个内嵌精简版的 JVM,包含异常处理,同步,线程管理,内存管理(垃圾回收)和 JNI 等组件。

      SubstrateVM 的启动时间非常短,内存开销非常少。用这种方式编译出的 Java 程序的执行时间可与C语言持平。

    3. 语言互操作性

      Graal VM 被官方称为“Universal VM”和“Polyglot VM”,这是一个在 HotSpot 虚拟机基础上增强而成的跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用,这里“任何语言”包括了 Java、Scala、Groovy、Kotlin 等基于 Java 虚拟机之上的语言,还包括了 C、C++、Rust 等基于 LLVM 的语言,同时支持其他像 JavaScript、Ruby、Python 和 R 语言等等。Graal VM 可以无额外开销地混合使用这些编程语言,支持不同语言中混用对方的接口和对象,也能够支持这些语言使用已经编写好的本地库文件。

    GraalVM在IoT边缘侧实现落地,效果显著!

    1、安装无需JVM环境,30000行项目安装仅需16M内存占用,可在mac、linux-arm64、windows和树莓派等硬件环境上运行;

    2、项目启动时间节省99%, 仅需10ms!享受极速的软件体验;

    安装

    graalvm-list.png

    GraalVM在 SDKMAN上有多个版本,可通过sdk list java进行查看,我这里使用的是22.2 社区版本

    安装SDKMAN!, 请看安装文档

    sdk install java 22.2.r11-grl

    上述GraalVM安装完成后,需要Native Image组件,使用下述命令行进行安装:

    Native Image安装

    gu install native-image

    Maven项目设置

    详细graalvm-maven-plugin使用简介见:https://graalvm.github.io/native-build-tools/latest/maven-plugin-quickstart.html

    <profile>
                <id>nativeid>
                <activation>
                    <property>
                        <name>nativename>
                    property>
                activation>
                <build>
                    <plugins>
                        <plugin>
                            <groupId>org.graalvm.buildtoolsgroupId>
                            <artifactId>native-maven-pluginartifactId>
                            <version>${graalvm-buildtools.version}version>
                            <extensions>trueextensions>
                            <executions>
                                <execution>
                                    <id>build-nativeid>
                                    <goals>
                                        <goal>compile-no-forkgoal>
                                    goals>
                                    <phase>packagephase>
                                execution>
                            executions>
                            <configuration>
                                <skip>falseskip>
                                <imageName>${appName}imageName>
                                <requiredVersion>${graalvm.version}requiredVersion>
                                <mainClass>${mainClass}mainClass>
                                <buildArgs>
                                    <arg>--no-fallbackarg>
                                    <arg>-Dfile.encoding=UTF-8arg>
                                    <arg>-H:-CheckToolchainarg>
                                    <arg>-H:+ReportExceptionStackTracesarg>
                                buildArgs>
                            configuration>
                        plugin>
                    plugins>
                build>
            profile>
    

    当然GraalVM的 AOT提前编译技术也不是完全没有缺点,其对于多语言、反射和字节码动态加载技术需要相对繁琐的配置。

    native-image缩略图.png

    src/main/resources文件夹下有

    • native-image.properties //native image配置信息文件
    • reflection-config.json //native 需要反射的类配置文件

    具体配置信息详情见: https://www.graalvm.org/22.0/reference-manual/native-image/Reflection/#manual-configuration

    native-image.properties信息如下:

    Args = -H:ReflectionConfigurationResources=${.}/reflection-config.json \
           -H:IncludeLocales=zh,en,de,fr \
           --initialize-at-run-time=io.netty.buffer.AbstractReferenceCountedByteBuf \
           --initialize-at-run-time=io.netty.buffer.ByteBufAllocator \
           --initialize-at-run-time=io.netty.buffer.ByteBufUtil \
           --initialize-at-run-time=io.netty.buffer.ByteBufUtil$HexUtil \
           --initialize-at-run-time=io.netty.buffer.PooledByteBufAllocator \
           --initialize-at-run-time=io.netty.buffer.UnpooledHeapByteBuf \
           --initialize-at-run-time=io.netty.buffer.UnreleasableByteBuf \
           --initialize-at-run-time=io.netty.util.AbstractReferenceCounted \
           --initialize-at-run-time=io.netty.util.internal.ThreadLocalRandom \
           --initialize-at-run-time=io.netty.util.concurrent.GlobalEventExecutor \
           --initialize-at-run-time=io.netty.util.concurrent.ImmediateEventExecutor \
           --initialize-at-run-time=io.netty.util.concurrent.ScheduledFutureTask \
           -Dio.netty.noUnsafe=true \
           -Dio.netty.leakDetection.level=DISABLED
    

    reflection-config.json信息如下:

    [
      {
        "name": "io.netty.channel.socket.nio.NioSocketChannel",
        "methods": [
          { "name": "", "parameterTypes": [] }
        ]
      },
    ......
    {
        "name" : "iot.technology.client.toolkit.nb.service.mobile.domain.action.data.MobCachedCommandResponse",
        "allDeclaredMethods" : true,
        "allPublicMethods" : true,
        "allDeclaredClasses" : true,
        "allPublicClasses" : true,
        "allDeclaredConstructors" : true,
        "allPublicConstructors" : true
      }
    ]
    

    上述信息配置完成,我们在项目根目录下执行以下命令:

    mvn clean package -Pnative
    

    会见到下面的日志命令输出:

    [INFO] --- native-maven-plugin:0.9.17:compile-no-fork (build-native) @ toolkit-app ---
    [INFO] Found GraalVM installation from JAVA_HOME variable.
    ......
    ========================================================================================================================
    GraalVM Native Image: Generating 'toolkit' (executable)...
    ========================================================================================================================
    [1/7] Initializing...                                                                                    (9.2s @ 0.20GB)
     Version info: 'GraalVM 22.2.0 Java 17 CE'
     Java version info: '17.0.4+8-jvmci-22.2-b06'
     C compiler: cc (null, null, 0.0.0)
     Garbage collector: Serial GC
     1 user-specific feature(s)
     - org.graalvm.home.HomeFinderFeature: Finds GraalVM paths and its version number
    [2/7] Performing analysis...  [*********]                                                               (45.3s @ 2.77GB)
      11,123 (90.04%) of 12,354 classes reachable
      18,642 (59.17%) of 31,506 fields reachable
      57,906 (57.83%) of 100,137 methods reachable
         524 classes,   194 fields, and 2,046 methods registered for reflection
          79 classes,   196 fields, and   136 methods registered for JNI access
           5 native libraries: -framework CoreServices, -framework Foundation, dl, pthread, z
    [3/7] Building universe...                                                                               (4.7s @ 1.45GB)
    [4/7] Parsing methods...      [**]                                                                       (3.6s @ 1.88GB)
    [5/7] Inlining methods...     [***]                                                                      (2.2s @ 3.94GB)
    [6/7] Compiling methods...    [*****]                                                                   (31.6s @ 4.19GB)
    [7/7] Creating image...                                                                                  (5.6s @ 2.77GB)
      25.79MB (49.67%) for code area:    37,581 compilation units
      25.84MB (49.78%) for image heap:  290,531 objects and 175 resources
     292.13KB ( 0.55%) for other data
      51.92MB in total
    ------------------------------------------------------------------------------------------------------------------------
    Top 10 packages in code area:                               Top 10 object types in image heap:
       1.65MB sun.security.ssl                                     5.53MB byte[] for code metadata
    1021.89KB java.util                                            2.78MB java.lang.String
     919.87KB picocli                                              2.73MB java.lang.Class
     731.85KB com.sun.crypto.provider                              2.62MB byte[] for general heap data
     565.20KB java.lang.invoke                                     2.42MB byte[] for java.lang.String
     518.23KB java.lang                                            1.46MB byte[] for embedded resources
     504.26KB org.jline.reader.impl                              955.88KB com.oracle.svm.core.hub.DynamicHubCompanion
     476.05KB c.s.org.apache.xerces.internal.impl.xs.traversers  663.69KB byte[] for reflection metadata
     458.58KB sun.security.x509                                  623.30KB java.util.HashMap$Node
     438.98KB com.sun.org.apache.xerces.internal.impl            580.77KB java.lang.String[]
      18.36MB for 389 more packages                                4.51MB for 2478 more object types
    ------------------------------------------------------------------------------------------------------------------------
                            6.3s (5.7% of total time) in 32 GCs | Peak RSS: 5.93GB | CPU load: 5.50
    ------------------------------------------------------------------------------------------------------------------------
    Produced artifacts:
    ......
    

    GraalVM带来的惊人效果

    启动时间

    启动时间代码添加:

    public static void main(String[] args) {
            long begin = System.currentTimeMillis();
            ......
            try {
                ......
                long end = System.currentTimeMillis();
                long runTime = end - begin;
                System.out.println("startUp cost runTime: " + runTime);
                ......
            } finally {
                ......
            }
        }
    

    使用IDEA启动代码,启动时间如下:

    启动时间-IDEA.png

    AOT提前编译后native-image启动时间:

    ➜  target git:(develop-v0.6.6) ✗ ./toolkit
    ......
    startUp cost runTime: 11
    

    占用内存的效果

    项目代码统计:

    代码统计.png

    编译后可在linux、树莓派、mac系统和windows系统可运行的镜像;

    native-image.png

    上述的项目配置和落地实践项目见: https://github.com/IoT-Technology/IoT-Toolkit

    结束语

    GraalVM对于JVM的开发人员来说,无疑是个非常好的消息。但是留给JVM的时间真的不多了。

  • 相关阅读:
    八核智能视频分析边缘计算盒子,4.8T+24路1080P视频解码
    异步编程 - 11 Spring WebFlux的异步非阻塞处理
    【ubuntu】中文输入法设置
    List 接口
    01 MongoDB的概述、应用场景、下载方式、连接方式和发展历史等
    创建我的空间和发帖功能
    vue项目实际开发的问题及实用技巧分享(二)
    多维数据可视化技术,Radviz可视化原理,向量化的 Radviz(vectorized Radviz,简称 VRV)
    工业和信息化部公布45个国家先进制造业集群名单
    python:神经网络的卷积核,权重矩阵长什么样子?
  • 原文地址:https://www.cnblogs.com/sanshengshui/p/17351623.html