GraalVM基础教程
前置知识什么是GraalVM?或访问链接:juejin.cn/post/716328…
1、安装与配置
熟悉安装JDK环境与配置的同学,安装GraalVM过程和jdk类似,因为GraalVM本身就是基于JDK对应的版本的,所有我们可以暂时理解就是安装一个多功能的JDK。废话不多说,开搞。
我这里示例是mac电脑,windows系统及Linux系统请参考安装教程
1.1、下载安装包
下载地址,找到自己使用的jdk版本对应的整合版本下载,根据自己的操作系统及系统架构下载对应的版本,如下图:
我这里下载的是macOS arm64位的graalvm-ce-java11-darwin-aarch64-22.3.0.tar.gz。
linux或者mac系统如果不知道,可以通过如下命令查看
uname -a
1.2、解压安装包
- 通过如下命令进行解压
tar -xvzf graalvm-ce-java11-darwin-aarch64-22.3.0.tar.gz
- 将解压后的文件目录移到java虚拟机目录,mac的java虚拟机目录为/Library/Java/JavaVirtualMachines/如果不知道的可以通过如下命令查看。
查看本机安装的jdk列表
/usr/libexec/java_home -V
移动解压后的GraalVM目录到java虚拟机安装目录
sudo mv graalvm-ce-java11-22.3.0 /Library/Java/JavaVirtualMachines/
- 配置环境变量
对于多JDK环境可以在~/.bash_profile文件中进行配置,这里可以参考如下配置通过别名进行快速切换。参考文档
### JDK definition
JAVA_08=$(/usr/libexec/java_home -v 1.8)
JAVA_11=$(/usr/libexec/java_home -v 11)
JAVA_17=$(/usr/libexec/java_home -v 17)
JAVA_19=$(/usr/libexec/java_home -v 19)
GRAALVM_11="/Library/Java/JavaVirtualMachines/graalvm-ce-java11-22.3.0/Contents/Home"
THE_F=~/.bash_profile
### alias
alias java8='sed -i "" "/export JAVA_HOME=\//d" $THE_F && echo "export JAVA_HOME=$JAVA_08" >> $THE_F && source $THE_F && echo "switch to Java8"'
alias java11='sed -i "" "/export JAVA_HOME=\//d" $THE_F && echo "export JAVA_HOME=$JAVA_11" >> $THE_F && source $THE_F && echo "switch to Java11"'
alias java17='sed -i "" "/export JAVA_HOME=\//d" $THE_F && echo "export JAVA_HOME=$JAVA_17" >> $THE_F && source $THE_F && echo "switch to Java17"'
alias java19='sed -i "" "/export JAVA_HOME=\//d" $THE_F && echo "export JAVA_HOME=$JAVA_19" >> $THE_F && source $THE_F && echo "switch to Java19"'
alias graalvm='sed -i "" "/export JAVA_HOME=\//d" $THE_F && echo "export JAVA_HOME=$GRAALVM_11" >> $THE_F && source $THE_F && echo "switch to Graalvm"'
修改后保存并推出,然后进行执行刷新命令就可以进行切换了
source ~/.bash_profile && graalvm
- 验证
查看jdk版本,可以看出配置生效了。
java -version
2、实战
2.1、IDEA运行项目
- 创建项目
我们在IDEA中创建一个springboot 的graalvm-demo项目,sdk选择我们上面配置的graalvm,先验证下,配置是否生效,项目能否跑起来。
- 写一个测试接口
@RestController
@RequestMapping
public class HelloController {
@RequestMapping("/hello")
public JSONObject hello() {
JSONObject data = new JSONObject();
data.put("code", "000000");
data.put("message", "处理成功");
return data;
}
}
- 运行项目
运行后,我们发现使用了新的sdk环境,而且整个应用启动非常快速,仅仅用了0.679s。
- 接口测试
请求一下我们刚写的接口http://localhost:8080/hello,发现服务正常。
至此基本环境算是搭建完成了。
2.2、多语言支持验证
在上一篇文章中文我们说了Graalvm有个特性是支持多语言运行,如下图Java,JavaScript,python,Ruby,R,WASM。下面我们就来说说熟悉的语言JavaScript在Graalvm中的运行执行情况。
2.2.1 运行JavaScript
2.2.1.1、组件安装
Graalvm运行js脚本,需要安装对应的组件(Since GraalVM 22.2, the JavaScript support is packaged in a separate GraalVM component,意思是从22.2版本开始拆分为单独的组件的),上图中的语言需要支持,都需要安装对应的依赖环境组件。我们可以通过Graalvm提供的gu工具进行安装。
- 查看可用组件
gu available
- 安装js组件
因为要从github下载,所以网络很重要,可能很慢。
gu install js
- 验证安装情况
查看已安装组件
gu list
2.2.1.2、验证
在java代码中运行javascript脚本。
- 简单打印
package com.jianjang.graalvm.demo.js;
import org.graalvm.polyglot.Context;
/**
* @program: graalvm-demo
* @description: 测试用例1
* @author: Jian Jang
* @create: 2022-11-12 22:06:36
*/
public class JavaScriptDemo1 {
public static void main(String[] args) {
Context context = Context.create();
context.eval("js", "console.log('Hello GraalVM! from JavaScript')");
}
}
执行结果
- 有返回值的js函数调用
package com.jianjang.graalvm.demo.js;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
/**
* @program: graalvm-demo
* @description: 测试用例2
* @author: Jian Jang
* @create: 2022-11-12 22:06:36
*/
public class JavaScriptDemo2 {
/***
* 运行脚本
* 注意:这里()包着,固定约束
*/
private static String JS_CODE = "(function hello(param){console.log('hello '+param); return new Date();})";
public static void main(String[] args) {
Context context = Context.create();
//JS 带参数及返回值
Value value = context.eval("js", JS_CODE);
Value result = value.execute("Jack");
System.out.println(result.toString());
}
}
执行结果
- 运行指定js文件中的函数
我这里写了一个dateUtils.js工具类,里面有一个常量和两个函数分别是auth,getDate和getTimeStamp,源码如下
/***
* 定义常量auth
* @type {string}
*/
const auth='Jian Jang';
/***
* 获取当前日期
* @returns {string}
*/
function getDate(){
var timestamp = Date.parse(new Date());
var date = new Date(timestamp);
//获取年份
var Y =date.getFullYear();
//获取月份
var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1);
//获取当日日期
var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
return Y+'-'+M+'-'+D;
}
/***
* 获取当前时间戳
* @returns {number}
*/
function getTimeStamp(){
return new Date().getTime();
}
java代码
package com.jianjang.graalvm.demo.js;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.IOException;
/**
* @program: graalvm-demo
* @description: 测试用例3
* @author: Jian Jang
* @create: 2022-11-12 22:06:36
*/
public class JavaScriptDemo3 {
/***
* 运行脚本
*/
public static void main(String[] args) throws IOException {
Context context = Context.create();
//执行指定js文件中的方法
File resource = new ClassPathResource("dateUtils.js").getFile();
Source fibSrc = Source.newBuilder("js", resource).build();
context.eval(fibSrc);
//获取js文件中的成员关键字
System.out.println("Polyglot JS members are:" + context.getBindings("js").getMemberKeys());
Value auth = context.getBindings("js").getMember("auth");
System.out.println(auth.toString());
//找到执行函数
Value getDate = context.getBindings("js").getMember("getDate");
Value result = getDate.execute(new String[]{});
System.out.println(result.toString());
Value getTimeStamp = context.getBindings("js").getMember("getTimeStamp");
Value result2 = getTimeStamp.execute(new String[]{});
System.out.println(result2.asLong());
}
}
运行结果
2.2.2、运行Python
对于不同语言的支持,需要安装对应的组件,python就要安装python组件。
2.2.2.1、组件安装
- 安装python组件
gu install python
- 验证安装情况
gu list
2.2.2.2、验证
- 简单函数调用
package com.jianjang.graalvm.demo.js;
import org.graalvm.polyglot.Context;
import java.io.IOException;
/**
* @program: graalvm-demo
* @description: 测试用例1
* @author: Jian Jang
* @create: 2022-11-12 22:06:36
*/
public class PythonDemo1 {
/***
* 运行脚本
*/
public static void main(String[] args) throws IOException {
Context context = Context.create();
context.eval("python", "print('Hello GraalVM! from Python')");
}
}
运行结果
- 运行指定的py文件中的函数
写一个python验证脚本,里面包含一个获取当前日期的函数getDate脚本如下:
#!/usr/bin/env python
# coding=utf-8
import datetime
def getDate():
return datetime.date.today().strftime('%Y-%m-%d')
java调用
package com.jianjang.graalvm.demo.js;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.IOException;
/**
* @program: graalvm-demo
* @description: 测试用例2
* @author: Jian Jang
* @create: 2022-11-12 22:06:36
*/
public class PythonDemo2 {
/***
* 运行脚本
*/
public static void main(String[] args) throws IOException {
Context context = Context.create();
//执行指定python文件中的方法
File resource = new ClassPathResource("MyFunc.py").getFile();
Source fibSrc = Source.newBuilder("python", resource).build();
context.eval(fibSrc);
//获取python文件中的成员关键字
System.out.println("Polyglot python members are:" + context.getBindings("python").getMemberKeys());
//找到执行函数
Value getDate = context.getBindings("python").getMember("getDate");
Value result = getDate.execute(new String[]{});
System.out.println("getDate="+result.toString());
}
}
运行结果
备注:其他语言的验证,此处略,大致思路是差不多的。
3、Native Image
Native Image是一种将Java代码提前编译为二进制文件的技术,即本机可执行文件。本机可执行文件仅包括运行时所需的代码,即应用程序类、标准库类、语言运行时和JDK中静态链接的本机代码。此处我们是不是想到了docker镜像,docker镜像:包括应用本身,及引用运行所依赖的环境,组件等。他们似乎在某些方面的原理是相同的。所以Native image也可以翻译成原生镜像,不同的操作系统性生成的都是本机的可执行文件(windows下的exe应用,Linux下的可执行应用),不同点就是,docker 镜像可以在不同的系统环境下的docker中运行。docker本身屏蔽了系统的差异。
3.1、Native Image的优点
- Uses a fraction of the resources required by the Java Virtual Machine, so is cheaper to run
使用Java虚拟机所需资源的一小部分,因此运行起来更容易(便宜)
- Starts in milliseconds
以毫秒为单位启动
- Delivers peak performance immediately, with no warmup
立即提供最高性能,无需预热
- Can be packaged into a lightweight container image for fast and efficient deployment
可以打包成轻量级容器映像,以实现快速高效的部署
- Presents a reduced attack surface
呈现减少的攻击面
3.1.1、镜像工具类
- 安装工具native-image
gu install native-image
- 检查安装情况
gu list
- 语法
通过如下命令可查看native-image的用法说明,引文版,此处不做过多解释,请自行查看。
native-image --help
This tool can ahead-of-time compile Java code to native executables.
Usage: native-image [options] class [imagename] [options]
(to build an image for a class)
or native-image [options] -jar jarfile [imagename] [options]
(to build an image for a jar file)
or native-image [options] -m <module>[/<mainclass>] [options]
native-image [options] --module <module>[/<mainclass>] [options]
(to build an image for a module)
3.1.2、验证
3.1.2.1、普通的java类编译成可执行文件
- 创建文件
将一个含有main方法的普通java类编译成可执行文件。首先随便找一个目录,确保当前用户权限足够,创建一个HelloWorld.java文件java代码如下:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, Native World!");
}
}
- 编译java文件
javac HelloWorld.java
- 创建可执行文件
native-image HelloWorld
执行结果如下
- 查看生成的文件
ll
我们可以看出当前目录下多了一个helloword可执行文件,同时生成了一个helloword.build.artifacts.txt文件
- 运行一下看看
执行可执行文件,我们看到,打印了我们上面main方法中的结果。
- helloword.build.artifacts.txt内容
我们看到里面几乎没啥东西,只有个可执行文件名称的标识,如下图。
3.1.2.2、springboot项目编译成可执行应用
- 创建验证接口
使用上面我们创建的springboot项目graalvm-demo,然后改造一下里面的hello接口,改成sayHello。代码如下:
package com.jianjang.graalvm.demo.controller;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: graalvm-demo
* @description: 测试类
* @author: Jian Jang
* @create: 2022-11-11 15:03:31
*/
@RestController
@RequestMapping
public class HelloController {
/***
* sayHello
*/
@RequestMapping(value = "/sayHello", method = {RequestMethod.GET, RequestMethod.POST})
public JSONObject sayHello(@RequestParam("name") String name) {
JSONObject data = new JSONObject();
data.put("code", "000000");
data.put("message", "hello " + name);
return data;
}
}
- 打包项目jar
进入项目根目录执行如下命令
mvn clean package
- 编译成可执行应用
进入jar包所在目录,target目录( /Users/zhangjian/works/workspace_home/graalvm-demo/target/graalvm-demo.jar),然后执行如下脚本
native-image -jar graalvm-demo.jar graalvm-demo
有上面日志可以看出,镜像生成失败了,但是生成了一个回滚镜像,这个镜像的运行,任然依赖于JVM,至于啥原因,我们后面再说。
- 验证应用
我们在target下可以看到过了一个可执行应用graalvm-demo,如下图:
- 运行应用
./graalvm-demo
由此看出,应用项目启动起来了,用了995毫秒,可以说是很快了。
- 接口验证
新开个终端,执行下面命令
curl localhost:8080/sayHello\?name\=zhangjian
本文项目代码:github.com/JianJang201…
至此java拥抱云原生的GraalVM基础就算完了,后面我们继续深入实践。如有问题,可联系我修改正。谢谢。