首页 > 编程语言 >java 打 graalvm native 包

java 打 graalvm native 包

时间:2024-08-07 11:30:08浏览次数:13  
标签:java String System maven native graalvm

 最近v2ex上java的热门话题是java太占内存了balabala,就挺感慨,java当年从一个在家电之类的小型嵌入设备语言,发展到今天的“企业级”应用语言,确实变成吃内存大户了,当然如今的环境下,多个百十来M的内存,和jvm、spring全家桶带给我们的开发便利与稳定性相比,就不值一提了。几篇帖子的测试表明,java裸跑一个hello world都要消耗30m的内存,在一些极端场景(比如跑在内存极其苛刻的物联网设备上),还是有些无法接受。

所以,本文介绍了基于graalvm打native包的方式,为您提供一种在需要轻量级环境下运行java程序的选择。

native优缺点

native包的优点:启动快、占用内存少;不用java环境(windows下直接双击exe运行,linux下敲./xxx名运行)

native包的缺点:没有JIT的优化,性能通常会差一些;不能一处编译到处运行了,需要打多个平台的native包;反射等代码处理起来比较麻烦

 

环境准备

windows平台

1)安装graalvm jdk

https://www.graalvm.org/downloads/#  下载windows版的graalvm jdk,

然后解压,像普通jdk一样配置环境变量:

这里把GRAALVM_HOME也配上,不然后面用命令行编译的话会报找不到GRAALVM_HOME

2)安装VisualStudio

要编译成.exe文件,必须做这一步,否则后面会报错"native-image-xxx.args xxx returned non-zero result"

去这个地址下载

https://visualstudio.microsoft.com/zh-hans/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false

 

然后安装时勾选“使用C++的桌面开发”,子选项里把“MsVC v143 - vS 2022C++ x64/x86生成工...”、“Windows 10 SDK (10.0.20348.0)”这俩勾上,如果你是win11就勾11的。

然后一路下一步,安装完后重启电脑,我们的环境就准备完成了。

linux平台

 linux下就简单多了, https://www.graalvm.org/downloads/#  下载linux版的graalvm jdk,解压后 vi ~/.bashrc添加如下环境变量

export JAVA_HOME=/mydata/java/graalvm-jdk-21
export GRAALVM_HOME=/mydata/java/graalvm-jdk-21/

export JAVA_BIN=$JAVA_HOME/bin
export JAVA_LIB=$JAVA_HOME/lib
export CLASSPATH=.:$JAVA_LIB/tools.jar:$JAVA_LIB/dt.jar
export PATH=$JAVA_BIN:/mydata/java/maven/bin:$PATH
source ~/.bashrc 让环境变量生效就ok了。 (为了方便管理,我是单独建了一个graalvm来搞)

编写一个helloworld

新建一个maven项目,修改下pom文件:

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

    <groupId>org.example</groupId>
    <artifactId>nativedemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <packaging>jar</packaging>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jdk.version>21</jdk.version>
        <release.version>21</release.version>
        <mainClass>t1.MemTest</mainClass>
        <native.maven.plugin.version>0.10.2</native.maven.plugin.version>
    </properties>

    <repositories>
        <repository>
            <id>central</id>
            <url>https://repo.maven.apache.org/maven2</url>
        </repository>
    </repositories>

    <build>
        <plugins>
            <!-- 打普通jar包 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>${mainClass}</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- 打native包 -->
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <version>${native.maven.plugin.version}</version>
                <configuration>
                    <mainClass>${mainClass}</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <pluginRepositories>
        <pluginRepository>
            <id>graalvm-native-build-tools-snapshots</id>
            <name>GraalVM native-build-tools Snapshots</name>
            <url>https://raw.githubusercontent.com/graalvm/native-build-tools/snapshots</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

再添加一个测试类

package t1;

public class MemTest {
    public static void main(String[] args) throws InterruptedException {
        //写一段测耗时的代码
        System.out.println("Starting memory test");
        long startTime = System.currentTimeMillis();
        double sum = 0;
        for (long i = 0; i < 10000000000L; i++) {
            sum += Math.sqrt(i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println(sum + "\nMemory test completed in " + (endTime - startTime) + " ms");
        //sleep主线程,方便测内存
        Thread.sleep(Long.MAX_VALUE);
    }
}

好了,idea里maven reload一下,就可以点这两个按钮来分别编译jar包或native包了

也可以直接运行如下命令编译jar包或native包

#jar
mvn package

#native
mvn org.graalvm.buildtools:native-maven-plugin:build

两个命令没有前后关系,可以独立运行

打包完成后,target目录下得到如下产物:

native只有6m的可独立运行文件,跑起来占用大约6-8m,效果还是比较好的。

但是运行耗时会稍微变长一点:

如果需要在本地包运行时添加jvm参数,或者传参到main方法的args中,可以这样:

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Starting memory test");
        for (String arg : args) {
            System.out.println(arg);
        }
        long maxMemory = Runtime.getRuntime().maxMemory();
        System.out.println("Xmx "+(maxMemory / (1024 * 1024)) +"m");
...
nativedemo.exe -Xmx1g hello
#打印
#hello
#Xmx 1024 m

解决反射

我们把demo改一下,用反射来加载一个方法并调用,按上面的配置直接打包并执行 nativedemo.exe t2.Service1 的话会报错  ClassNotFoundException:

package t1;


import java.lang.reflect.Method;

public class MemTest {
    public static void main(String[] args) throws Exception {
        System.out.println("Starting memory test 2");
        Class<?> clazz = Class.forName(args[0]);
        Object instance = clazz.getConstructor().newInstance();
        Method method = clazz.getMethod("hello", String.class);
        String res = (String) method.invoke(instance, "world");
        System.out.println(res);
        Thread.sleep(Long.MAX_VALUE);
    }
}
package t2;

public class Service1 {
    public String hello(String name) {
        return "hello " + name;
    }
}

此时,我们需要新建一个文件src/main/resources/META-INF/native-image/reflect-config.json,并在其中指明我们需要反射操作的类:

[
  {
    "name" : "t2.Service1",
    "allDeclaredConstructors" : true,
    "allPublicMethods" : true
  }
]

整体文件结构如下:

再次编译(有时候需要先clean、package一下),运行nativedemo.exe t2.Service1 就能成功反射到了。

另,获取native包所在路径时,用ClassLoader行不通,可以用如下代码:

        File currentFile = new File(MemTest.class.getProtectionDomain().getCodeSource().getLocation().toURI());
        String currentDir = currentFile.getParent();
        System.out.println("Current executable directory: " + currentDir);

 

标签:java,String,System,maven,native,graalvm
From: https://www.cnblogs.com/imliuyu/p/18346720

相关文章