前置知识
什么是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文件中进行配置,这里可以参考如下配置通过别名进行快速切换。参考文档
#
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 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.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();
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();
File resource = new ClassPathResource("dateUtils.js").getFile();
Source fibSrc = Source.newBuilder("js", resource).build();
context.eval(fibSrc);
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脚本如下:
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();
File resource = new ClassPathResource("MyFunc.py").getFile();
Source fibSrc = Source.newBuilder("python", resource).build();
context.eval(fibSrc);
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
执行结果如下
查看生成的文件
我们可以看出当前目录下多了一个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基础就算完了,后面我们继续深入实践。如有问题,可联系我修改正。谢谢。