语法层⾯新特性
1、⽂本块
⽂本块功能,⽂本块指多⾏的字符串,使⽤连续的三个双引号来包围⼀段带换⾏的⽂字,它避免了换⾏转义的需要,并⽀持String.format。
同时添加了两个新的转义字符:
- : , 置于⾏尾,⽤来将两⾏连接为⼀⾏
- \s: 单个空⽩字符
示例代码:
String query =
"""
SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB` \s
WHERE `CITY` = '%s' \
ORDER BY `EMP_ID`, `LAST_NAME`;
""";
System.out.println("===== query start =====");
System.out.println(String.format(query, "合肥"));
System.out.println("===== query stop =====");
打印结果:
SELECT EMP_ID, LAST_NAME FROM EMPLOYEE_TB
WHERE CITY = '合肥' ORDER BY EMP_ID, LAST_NAME;
2 、Switch 表达式增强
从 JDK8 到 JDK17,Switch 表达式做了很⼤的增强,再也不是简单的if-else的替代品了。
扩展switch语句,使其既可以作为语句使⽤,也可以作为表达式使⽤,并且两种形式都可以⽤“传统”或“简化”的作⽤域和控制流⾏为。同时添加了yield关键字,提供break 与switch返回值的功能。
示例 1:可以将多个匹配写到⼀起。
switch (name) {
case "李⽩", "杜甫", "⽩居易" -> System.out.println("唐代诗⼈");
case "苏轼", "⾟弃疾" -> System.out.println("宋代诗⼈");
default -> System.out.println("其他朝代诗⼈");
}
示例 2:每个分⽀直接返回⼀个值。
int tmp = switch (name) {
case "李⽩", "杜甫", "⽩居易" -> 1;
case "苏轼", "⾟弃疾" -> 2;
default -> {
System.out.println("其他朝代诗⼈");
yield 3;
}
};
3、instanceof的模式匹配
instances 增加了模式匹配的功能,如果变量类型经过instances判断能够匹配⽬标类型,则对应分⽀中⽆需再做类型强转。
示例代码:
if (o instanceof Integer i && i > 0) {
System.out.println(i.intValue());
} else if (o instanceof String s && s.startsWith("t")) {
System.out.println(s.charAt(0));
}
4、var 局部变量推导
对于某些可以直接推导出类型的局部变量,可以使⽤var进⾏声明。
var nums = new int[] {1, 2, 3, 4, 5};
var sum = Arrays.stream(nums).sum();
System.out.println("数组之和为:" + sum);
这个特性仁者⻅仁,智者⻅智。 Java 的强类型语法更能保护代码安全。
模块化及类封装
从 JDK8 开始,JDK 中陆续更新了很多应⽤相关的新特性。这些新特性⾥明显能够看出借鉴了很多新兴的动态语⾔的特征,让 Java 变得更年轻有活⼒了。
1、记录类 record
在 JDK17 中,可以声明⼀种特殊的类,record 。被reocrd定义的类代表的是⼀种不可变的常量,只能⽤来描述⼀种简单的不可变的数据结构。这样我们未来或许就不⽤再定义⼀⼤堆的 BO、 VO 、 DTO 这些只⽤来进⾏值传递的复杂对象了。
例如可以这样声明⼀个带有x,y两个属性的Point类。
public record Point(int x, int y) {
}
然后,这个类只能初始化设置属性值,初始化后,不允许修改属性值,⽤反射也不⾏。唯⼀和我们⾃⼰写的 POJO有点不同的是,获取属性的⽅法,与属性同名,⽽不再是getXXX这样的了。
public class RecordTest {
@Test
public void getPoint() throws IllegalAccessException {
Point p = new Point(10,20);
for (Method method : p.getClass().getMethods()) {
System.out.println(method);
}
for (Field field : p.getClass().getDeclaredFields()) {
System.out.println(field);
// 不允许通过反射修改值。
// field.setAccessible(true);
// field.set(p,30);
}
System.out.println(p.x()+"===="+p.y());
}
}
record记录类的实现原理,其实⼤致相当于给每个属性添加了private final声明。这样就不允许修改。另外,从字节码也能看到,对于record类,同时还实现了toString,hashcode,equals⽅法,⽽这些⽅法都被声明成了final,进⼀步阻⽌应⽤定制record相关的业务逻辑。
2 、隐藏类 Hidden Classes
从 JDK15 开始,JDK 引⼊了⼀个很有意思的特性,隐藏类。隐藏类是⼀种不能被其他类直接使⽤的类。隐藏类不再依赖于类加载器,⽽是通过读取⽬标类字节码的⽅式,创建⼀个对其他类字节码隐藏的class对象,然后通过反射的⽅式创建对象,调⽤⽅法。
我们先来⼀个示例理解⼀下什么是隐藏类,再来思考隐藏类有什么⽤处。
⽐如先编写⼀个普通的测试类
public class HiddenClass {
public String sayHello(String name) {
return "Hello, " + name;
}
public static void printHello(String name) {
System.out.printf("""
Hello, %s !
Hello, HiddenClass !
%n""", name);
}
}
传统⽅式下,要使⽤这个类,就需要经过编译,然后类加载的整个过程。但是隐藏类机制允许直接从编译后的class字节码⼊⼿,直接使用这个类。
⽐如,我们可以使⽤下⾯的⽅法获取class字节数组:
public void printHiddenClassBytesInBase64(){
//编译后的 class ⽂件地址
String classPath ="/Users/roykingw/DevCode/JDK17Demo/demoModule/target/classes/com/roy/hidden/HiddenClass.class";
try {
byte[] bytes = Files.readAllBytes(Paths.get(classPath));
System.out.println(Base64.getEncoder().encodeToString(bytes));
} catch (IOException e) {
e.printStackTrace();
}
}
这样就可以拿到⼀串编码后的class⽂件的字节码。接下来,就可以⽤这个字节码直接⽣成这个类。例如:
示例很简单,看似花里胡哨,但是其实,这将是以后开发框架⾮常重要的⼀个特性。因为这些隐藏类直接操作字节码,可以极⼤提⾼ Java 的动态语⾔能⼒。
实际上这也是 Java吸收其他语⾔优点的⼀种表现。近年来有很多基于 JVM 的语⾔都在强调动态语⾔。⽐如Scala,Kotlin 中⼤量运⽤匿名函数,Java ⾃⼰的 Lambda 表达式,本质上也是⼀种匿名函数。这些匿名函数在语法层⾯并不需要提前声明,只要在运⾏时拿来⽤就可以了。但是在 JVM 中,Java ⼀切皆对象,这些匿名函数也必须经过类加载的繁琐过程,并且类的卸载也⾮常受限制。所以在 Spring 框架中,⼤量的运⽤了 ASM 这样的直接操作字节码的技术,就是为了加快这些动态对象的生命周期。但是这些技术方案的实现即麻烦⼜低效,⽽ JDK 中引⼊了隐藏类机制,就可以作为⽣成动态类的新标准。
3 、密封类 Sealed Classes
密封类在 JDK15 引⼊,到 JDK17 中正式转正。
在 JDK8 中,每⼀个类都可以被任意多个⼦类继承,并修改其中的内置功能。⽐如 JDK8 中最重要的类加载双亲委派机制,在应⽤当中,程序员可以随意挑选⼀个内置的类加载器,继承出新的类加载器实现,随意打破双亲委派机制。这其实是不太安全的,意味着很多内置的⾏为得不到保护。⽽密封类就是⽤来限制每⼀个⽗类可以被哪些⼦类继承或者实现。
⾸先在声明类或⽅法时,如果增加sealed修饰,那么还需要同时增加permits指定这个类可以被哪些类来继承或实现。例如:
public sealed abstract class Shape permits Circle, Rectangle, Square {
public abstract int lines();
}
接下来,Shape 的⼦类也会要收到限制。在声明类时,需要声明⾃⼰的密封属性。可以有三个选项:
- final,表示这个⼦类不能再被继承了。
- non-sealed 表示这个⼦类没有密封特性,可以随意继承。
- sealed 表示这个⼦类有密封特性。再按照之前的⽅式声明他的⼦类。 例如针对 Shape,就可以声明这样的⼀些⼦类:
// ⾮密封⼦类,可以随意继承
public non-sealed class Square extends Shape{
@Override
public int lines() {
return 4;
}
}
//final ⼦类,不可再被继承
public final class Circle extends Shape{
@Override
public int lines() {
return 0;
}
}
// 密封⼦类,继续声明他所允许的⼦类。
public sealed class Rectangle extends Shape permits FilledRectangle {
@Override
public int lines() {
return 3;
}
}
public final class FilledRectangle extends Rectangle{
@Override
public int lines() {
return 0;
}
}
⽐如对 JDK8 的类加载体系,就可以通过密封类机制,让⼦类只能从 SecureClassLoader或者 URLClassLoader往下继承,这样就可以保护SecureClassLoader和 URLClassLoader 中的安全⾏为。
密封类能够保护⽗类的安全⾏为,但是也有⼀些限制。⽗类和指定的⼦类必须在同⼀个显式命名的module下,并且⼦类必须直接继承⽗类。
4、模块化 Module System
这是从 JDK9 之后引⼊的⼀个重要机制,对于熟悉 JDK8 的开发者,这是⼀个颠覆性的⼤变⾰。如果你真的有升级JDK版本的打算,那么对于这个模块化机制,你⼀定不能只是简简单单的看下⽹上的介绍贴,必须要做好颠覆三观的准备。
1 、什么是模块化
JDK8 中,我们写的 Java 代码是在⼀个⼀个的package下⾯的,模块化在包之上增加了更⾼级别的聚合,它包括⼀组密切相关的包和资源以及⼀个新的模块描述符⽂件。简单点说,module是 java 中package包的上⼀层抽象。通过module,java 可以更精确的分配对象的⽣效范围。
⽐如,在 JDK8 的安装⽬录下,JDK 预设的功能是以⼀个⼀个jar包的形式存在的,但是在JDK17 的安装⽬录下,你就看不到那些jar包了,取⽽代之的,是⼀系列以jmod后缀的⽂件,这些就是⼀个⼀个的模块。
安装 JDK17 后,也可以使⽤java --list-modules 查看到所有的系统模块。
可以看到,整个 JDK 都已经⽤模块化的⽅式进⾏了重组,并且,在 JDK 的安装⽬录下,也已经取消了 JRE ⽬录。这意味着,在新版本的 JDK 中,应⽤程序可以定制⾃⼰的JRE,只选择应⽤所需要的模块,⽽不再需要引⼊整个JDK 庞⼤的后台功能。
⽐如,我们如果只需要使⽤java.base模块中的类型,那么随时可以⽤⼀下指令打包出⼀个可以在服务器上运⾏的 JRE:
jlink -p $JAVA_HOME/jmods --add-modules java.base --output basejre
这个basejre就可以像安装 JDK ⼀样部署到⼀台新的服务器上运⾏了。
2、声明⼀个module
引⼊模块化机制后,应⽤需要在每个模块的根⽬录下创建⼀个module-info.java⽂件,⽤来声明⼀个模块。然后在这个⽂件中,⽤module关键字,声明⼀个模块。例如:
module roy.demomodule {
}
这样,当前⽬录下的所有package下的代码,都将属于同⼀个module。module名字必须全局唯⼀。⾄于具体的格式,没有强制要求,不过通常的惯例是类似于包结构,全部⽤⼩写,⽤.连接。
接下来就需要在roy.demomodule中声明module的⼀些补充信息。这些补充信息主要包括:
- 对其他module的依赖关系
- 当前module对外开放的 API
- 使⽤和提供的服务
3 、require 声明module依赖
在module-info.java中⾸先要声明当前module需要依赖哪些外部模块。⽐如,如果你要使⽤junit,那么除了要在pom.xml中引⼊junit对应的依赖外,还需要在module-info.java中添加配置,否则项⽬编译就会报错。
requires junit;
这⾥要注意,对于显式声明了module-info.java的模块来说,模块名是显⽽易⻅的。但是对于没有声明module-info.java的⾮模块化jar包来说,默认就会创建具有jar包名称的模块。⽽这个名称还去掉版本号之后的标准包名。
⽐如,在demoModule1中,我引⼊了如下的junit依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
那么此时,从 Maven仓库下载下来的jar包是junit-4.13.2.jar。 那么此时,junit 的模块名就是junit。
另外,从 JDK9 开始,JDK 所有的内置基础代码都已经按照模块化进⾏了重组,所以,要使⽤ JDK 内置的功能,也同样需要通过requires声明所需要的依赖。⽐如,如果你要使⽤ JDBC 功能,那么就需要单独引⼊java.sql这个模块。
requires java.sql
当⼀个module需要另⼀个module的类型进⾏编译,但不想在运⾏时依赖它时,可以使⽤requires static 进⾏声明。如果 module A requires static module B,那么此时,A 就只需要 B 参与模块编译,在运⾏时,可以没有 B模块。类似于 Maven 当中的compile配置。
4、exports 和 opens 声明对外的 API
快 接下来,当要进⾏跨模块的功能访问时,需要在模块上声明模块对外的 API 。
例如,当我们想要使⽤junit构建⼀个单元案例时, 如果直接执⾏就会看到下⾯的报错提示:
package com.roy.language;
import org.junit.Test;
public class SwitchDemo {
@Test
public void demo1(){
String name="李⽩";
switch (name) {
case "李⽩", "杜甫", "⽩居易" -> System.out.println("唐代诗⼈");
case "苏轼", "⾟弃疾" -> System.out.println("宋代诗⼈");
default -> System.out.println("其他朝代诗⼈");
}
}
}
这就是因为这个单元案例实际上需要经过junit模块调⽤到当前的模块。当出现这种跨模块的调⽤时,就需要在模块中声明有哪些 API 是其他模块可以调⽤的。⽽声明的⽅式,就是提示当中的exports关键字。使⽤exports关键字可以对外开放哪些package。
另外,需要补充⼀下的是,exports关键字开放的成员在编译和运⾏时都可以访问,但是不能⽤反射的⽅式访问。如果想要通过反射的⽅式访问⼀个模块内的成员,需要改为使⽤opens关键字声明。声明⽅式和exports⼀样。
5 、uses 服务开放机制
基于模块化机制,JDK 还重新定制了 SPI 机制,来实现接⼝与服务实现类的解耦。
这⾥,我们就需要增加⼀个⾃定义模块来演示这种 SPI机制。⽐如,在下⾯这个示例中,就⽤ Maven 创建了两个模块,demoModule的模块名为roy.demomodule,demoModule2 的模块名为roy.demomodule2。在roy.demomodule模块中引入了roy.demomodule2模块。
接下来,在demoModule2 模块中,添加⼀个接⼝以及两个不同的实现类。
package com.roy.service;
public interface HelloService {
String sayHello(String name);
}
package com.roy.service.impl;
import com.roy.service.HelloService;
public class MorningHello implements HelloService {
@Override
public String sayHello(String name) {
return "good morning "+ name;
}
}
package com.roy.service.impl;
import com.roy.service.HelloService;
public class EveningHello implements HelloService {
@Override
public String sayHello(String name) {
return "good evening "+name;
}
}
这时,就可以在 demoModule2 模块中对外暴露⼀个HelloService的服务接⼝,并且选择对外暴露这个服务接⼝的⼀个或多个服务实现类。
module roy.demomodule2 {
exports com.roy.service;
provides com.roy.service.HelloService with
com.roy.service.impl.MorningHello,
com.roy.service.impl.EveningHello;
}
⽽在调⽤⽅,也就是demoModule模块中,可以在module-info.java⽂件中使⽤uses关键字声明要使⽤的服务。
module roy.demomodule {
requires roy.demomodule2;
uses com.roy.service.HelloService;
}
接下来,就可以在demoModule模块中,使⽤ SPI 机制直接调⽤另⼀个模块的服务。
public class ServiceDemo {
public static void main(String[] args) {
ServiceLoader<HelloService> services = ServiceLoader.load(HelloService.class);
for (HelloService service : services) {
System.out.println(service.sayHello("loulan"));
}
}
}
通过这种机制,可以实现服务之间的解耦。未来demoModule2可以通过调整module-info.java,快速替换新的服务实现类,⽽对调⽤⽅没有任何影响。
6 、构建模块化 Jar 包
当这些模块代码构建好了之后,就可以放到服务器上运⾏了。与模块化机制配套的,在java指令中,也增加了使⽤模块的参数。
例如,将我们之前演示的两个模块导出成jar包后,就可以通过以下参数在服务器执⾏:
(base) % java --module-path demoModule/demoModule.jar:demoModule2_jar/demoModule2.jar -m roy.demomodule/com.roy.spi.ServiceDemo
good morning loulan
good evening loulan
当然,也可以快速检索这些⽬录下的模块情况
(base) % java --module-path demoModule:demoModule2_jar --list-modules
jdk.unsupported@17.0.8
jdk.unsupported.desktop@17.0.8
jdk.xml.dom@17.0.8
jdk.zipfs@17.0.8
roy.demomodule
file:///Users/roykingw/DevCode/JDK17Demo/out/artifacts/demoModule/demoModule.jar
roy.demomodule2
file:///Users/roykingw/DevCode/JDK17Demo/out/artifacts/demoModule2_jar/demoModule2.jar
7 、类加载机制调整
与模块化机制对应,JDK9 往后的类加载机制也做了不⼩的调整。
在 JDK8 中的类加载体系可以简单概括为三点:
- JDK 中的类加载器分为BootstrapClassLoader,ExtClassLoader和AppClassLoader,通过parent属性构成亲属关系。
- 每个类加载器对应⼀个独⽴的加载⽬录,并对加载过的类保留⼀个缓存。
- 双亲委派机制,也就是加载⼀个类时,要向上委托查找,向下委托加载。
⽽使⽤模块化机制后,虽然 JDK 也依然在尽⼒兼容传统的类加载体系,但是,为了兼容模块化机制,JDK 还是对类加载体系做了⼏个⾮常明显的调整。
1 、⽤平台类加载器PlatformClassloader代替扩展类加载器 ExtClassLoader。
这是⼀个很⾃然的变化。以往保留ExtClassLoader是为了在 JDK 的标准实现之外,引⼊⼀些具有额外扩展功能的Jar 包。⽽使⽤模块化机制后,整个 JDK 都基于模块化进⾏了构建。由⼀系列 JMOD ⽂件构成的 JDK 已经天⽣就具备了可扩展的能⼒,⾃然也就不需要扩展类加载器了。
2 、调整类加载器的实现
以往ExtClassLoader和AppClassLoader都继承⾃URLClassLoader,现在PlatformClassLoader和AppClassLoader 都改为继承⾃BuildinClassLoader。在BuildinClassLoader中实现了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理。
另外,以往在 JDK 中看不到的BootstrapClassLoader,在新的架构下也已经有了明确的类来进⾏描述。只不过,为了保持与之前代码的兼容性,所有获取 BootstrapClassLoader 的场景(⽐如String.class.getClassLoader())还是会返回null,⽽不是BootstrapClassLoader实例。
3 、调整双亲委派机制
在 Java 模块化系统中明确规定了三个类加载器各⾃负责加载哪些系统模块。当PlatformClassLoader和AppClassLoader收到类加载请求时,在委派给⽗类加载器加载前,会先判断该类是否能够归属到某⼀个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责加载那个模块的加载器完成加载。
但是为了与之前的代码兼容,在⾃定义实现类加载器时,还是保持按照以前的双亲委派机制进⾏⼯作。
5 、GC调整
另外,从 JDK8 到 JDK17,还涉及到了⾮常多的调整,这些调整让 Java 具备了更多现代语⾔的特性,并且执⾏效率也在不断提⾼。
⽐如,重写了Socket 底层的 API 代码,让这写代码使⽤更简单,实现也更易于维护和替换。
默认禁⽤偏向锁。从 JDK1.6 开始,对synchronized关键字的实现就形成了 ⽆锁 ->偏向锁->轻量级锁->重量级锁的锁升级过程。⽽在 JDK15 中,默认就废弃了偏向锁。当然,⽬前还是可以通过添加参数 -XX:+UseBiasedLocking⼿动开启偏向锁。
不过 JDK 在升级过程中,⼀直保持着良好的向下兼容性,因此,有很多优化调整对开发⼯作影响都还不是太⼤。但是在这些调整当中,有⼀部分调整是⼤家需要额外关⼼⼀下的,那就是对于 GC 的调整。
1 、ZGC 转正
ZGC 在之前已经做过介绍,是现在最为强⼤的⼀个垃圾回收器,⾃ JDK11 开始引⼊,从 JDK15 开始正式投⼊了使⽤。现在使⽤-XX:+UseZGC参数就可以快速使⽤ ZGC 。
另外,ZGC 的具体实现其实也在版本升级过程中不断优化。在 JDK17 中使⽤指令 java -XX:+PrintFlagsFinal-version 可以简单看到,与 ZGC 相关的系统不稳定参数已经基本没有了。G1的还有⼀⼤堆 这也说明 ZGC 的算法优化已经相当成熟了。
随 ZGC 登场的,还有 RedHat 推出的Shenandoah 垃圾回收器。尽管 Oracle ⼀直⽐较抵触这个⾮官⽅推出的垃圾回收器,但是最终也还是将Shennandoah 垃圾回收器以可选的⽅案集成了进来。现在可以使⽤ -XX:+UseShenandoahGC 参数⼿动选择shennandoah。
2 、废除 CMS
虽然 CMS作为 G1 之前唯⼀的⼀款并发的垃圾回收器,在相当⻓的时间⾥,都扮演者⾮常重要的⻆⾊。在最为经典的 JDK8 时代,尽管 CMS ⼀直没有作为默认垃圾回收器登场过,但是关于 G1 和 CMS 的⽐较以及取舍,⼀直都是业界津津乐道的话题。但是,随着 G1 垃圾回收器发展得更为完善,以及后续ZGC,shennandoah等现代垃圾回收器开始登场,过于复杂的 CMS 垃圾回收器还是退出了历史舞台。
在 JDK14 中,就彻底删除了 CMS 垃圾回收器。与 CMS ⼀起退场的,还有Serial垃圾回收器。 SerialOld 这个最早的垃圾回收器其实早就应该退出历史舞台了,只不过由于他⼀直作为 CMS 的补充⽅案⽽⼀直保留。这次也终于随着 CMS⼀起退出了。
GraalVM 虚拟机
Graal 编译器以及由此诞⽣的GraalVM,虽然⽬前还处在实验阶段,但是也是 Java 程序员们必须要了解的,因为他未来极有可能替代 HotSpot,成为 Java⽣态的下⼀代技术基础。
1 、关于 Graal
Graal编译器最早是作为 HotSpot 的 C1 编译器的下⼀代编译器设计的,使⽤Java语⾔进⾏编写。2012年,Graal编译器才发展成为⼀个独⽴的 Java 编译项⽬。⽽早期的 Graal其实也和 C1,C2 ⼀样,需要与 HotSpot 虚拟机配合⼯作。但是随着 JDK9 开始推出 JVMCI(Java虚拟机编译器接⼝),才让 Graal 可以从 HotSpot 中独⽴出来,并逐渐形成了现在的 GraalVM 。
虽然你可能对 Graal 了解不多,但是,Graal 其实⼀直深受业界关注。Oracle 公司希望他能够发展成为⼀个更完美的编译器,⾼编译效率、⾼输出质量、⽀持提前编译和即时编译,同时⽀持应⽤于包括HotSpot在内的不同虚拟机。⽽使⽤ C \C++编写的 C1 和 C2 编译器,也逐渐变得越来越臃肿,维护和更新都更加困难。这时使⽤Java语⾔编写的 Graal ⾃然就成了⾸选 。
另外,在业务层⾯,Java 也急需⼀种更⾼效的编译器来迎合现在越来越⽕爆的云原⽣架构。现在作为 Java 主流的服务端版本总体上是⾯向⼤规模,⻓时间运⾏的系统设计的。像即时编译器(JIT)、性能优化、垃圾回收等代表性的特征都是⾯向程序⻓时间运⾏设计的,需要⼀段时间预热才能达到最佳性能,才能享受硬件规模提升带来的红
利。但是现在的微服务背景下,对服务的规模以及稳定性要求在逐渐降低,反⽽对容器化、启动速度、预热时间等⽅⾯提出了新的要求。⽽这些⽅⾯都是 Java的弱项。因此 Java 语⾔也需要这样⼀款新的虚拟机,来提升与很多新出来的现代语⾔,⽐如golang的竞争优势。
2 、使⽤ GraalVM
接下来我们来使⽤⼀下GraalVM。在 GraalVM 的官⽅⽂档中,⾸先有⼀段对于 GraalVM 的整体描述:
从这段整体描述就能看到,使⽤ GraalVM 和使⽤其他的 JDK,没有什么⼤的区别。所以,使⽤ GraalVM 的⽅式也是 下载-》配置环境变量-》编译-》执⾏ ⼏个步骤。
GraalVM 的官⽹地址是:www.graalvm.org 。官⽹上⽬前就可以下载对应版本的产品。当前有 Java17和 Java21两个版本。
JDK版本选择17,我当前的服务器是AArch64的Linux,选择对应的信息后即可下载。
下载下来后是⼀个tar包压缩⽂件。接下来跟安装jdk⼀样,解压,配置JAVA_HOME 环境变量,就可以⽤java -version进⾏测试了。这部分就略过了。⽐如我安装后的结果是这样的
[oper@localhost ~]$ java -version
java version "17.0.9" 2023-10-17 LTS
Java(TM) SE Runtime Environment Oracle GraalVM 17.0.9+11.1 (build 17.0.9+11-LTS
jvmci-23.0-b21)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.9+11.1 (build 17.0.9+11-LTS
jvmci-23.0-b21, mixed mode, sharing)
另外,在 GraalVM 中还提供了⼀个管理指令 gu