Skip to content

Java 9-17 特性

JDK 9

Stream 实例化方法

ofNullable()的使用

Java 8 中 Stream 不能完全为 null,否则会报空指针异常。而 Java 9 中的 ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。

java
//java 8: 报NullPointerException
//Stream<Object> stream1 = Stream.of(null);
//System.out.println(stream1.count());

//java 9: 不报异常,允许通过
Stream<String> stringStream = Stream.of("AA", "BB", null);
System.out.println(stringStream.count());//3

//java 9: 不报异常,允许通过
//ofNullable():允许值为null
Stream<Object> stream1 = Stream.ofNullable(null);

iterator()重载的使用

java
//原来的控制终止方式:
Stream.iterate(1,i -> i + 1).limit(10).forEach(System.out::println);

//现在的终止方式:
Stream.iterate(1,i -> i < 100,i -> i + 1).forEach(System.out::println);

Java 的 REPL 工具: jShell 命令

Java 终于拥有了像 Python 和 Scala 之类语言的 REPL 工具(交互式编程环境,read - evaluate - print - loop):jShell。以交互式的方式对语句和表达式进行求值。即写即得快速运行

利用 jShell 在没有创建类的情况下,在命令行里直接声明变量,计算表达式,执行语句。无需跟人解释”public static void main(String[] args)”这句"废话"。

bash
# cmd 命令窗口调出 jShell
C:\Program Files\Java\jdk-17.0.3.1\bin>jshell
|  欢迎使用 JShell -- 版本 17.0.3.1
|  要大致了解该版本, 请键入: /help intro
# 注意:此时前缀从 cmd 的文件路径前缀已经变成了 jshell> 前缀。
jshell> /help intro
|
|                                   intro
|                                   =====
|
|  使用 jshell 工具可以执行 Java 代码,从而立即获取结果。
|  您可以输入 Java 定义(变量、方法、类等等),例如:int x = 8
| Java 表达式,例如:x + x
| Java 语句或导入。
|  这些小块的 Java 代码称为“片段”。
|
|  这些 jshell 工具命令还可以让您了解和
|  控制您正在执行的操作,例如:/list
|
|  有关命令的列表,请执行:/help
# 直接输入代码(类似于浏览器F12控制台中,直接输入 js 代码)
jshell> int i = 10;
i ==> 10

jshell> int j = 12;
j ==> 12
# 按 Tab 键可以自动补全
jshell> System.out.println(i + j);
22
# 列出当前 session 里所有有效的代码片段
jshell> /list

   1 : int i = 10;
   2 : int j = 12;
   3 : System.out.println(i + j);
# 查看当前 session 下所有创建过的变量
jshell> /var
|    int i = 10
|    int j = 12
# 查看当前 session 下所有创建过的方法
jshell> /methods
|    int add(int, int)

# 退出 jshell
jshell> /exit
|  再见

C:\Program Files\Java\jdk-17.0.3.1\bin>

从外部文件加载源代码【HelloWorld.java】

java
public void hello() {
    System.out.println("Hello World!");
}
hello();

使用/open 命令调用

bash
jshell> /open D:\HelloWorld.java
Hello World!

jshell>

异常处理之 try-catch 资源关闭

JDK7 ~ JDK8 我们是这样处理的:

在 try 的后面可以增加一个(),在括号中可以声明流对象并初始化。try 中的代码执行完毕,会自动把流对象释放,就不用写 finally 了。

java
try (
        FileInputStream fis = new FileInputStream("d:/1.txt");
        InputStreamReader isr = new InputStreamReader(fis, "utf-8");
        BufferedReader br = new BufferedReader(isr);
    ) {
        String str;
        while ((str = br.readLine()) != null) {
            System.out.println(str);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

JDK9 的新特性

try 的前面可以定义流对象,try 后面的()中可以直接引用流对象的名称。在 try 代码执行完毕后,流对象也可以释放掉,也不用写 finally 了。

java
InputStreamReader reader = new InputStreamReader(System.in);
OutputStreamWriter writer = new OutputStreamWriter(System.out);
try (reader; writer) {
    //reader是final的,不可再被赋值
    //   reader = null;
} catch (IOException e) {
    e.printStackTrace();
}

JDK 10

局部变量类型推断

局部变量的显示类型声明,常常被认为是不必须的,给一个好听的名字反而可以很清楚的表达出下面应该怎样继续。本新特性允许开发人员省略通常不必要的局部变量类型声明,以增强 Java 语言的体验性、可读性。

java
//1.局部变量的实例化
var list = new ArrayList<String>();

var set = new LinkedHashSet<Integer>();

//2.增强for循环中的索引
for (var v : list) {
    System.out.println(v);
}

//3.传统for循环中
for (var i = 0; i < 100; i++) {
    System.out.println(i);
}

//4. 返回值类型含复杂泛型结构
var iterator = set.iterator();
//Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();
  • 不适用场景
    • 声明一个成员变量
    • 声明一个数组变量,并为数组静态初始化(省略 new 的情况下)
    • 方法的返回值类型
    • 方法的参数类型
    • 没有初始化的方法内的局部变量声明
    • 作为 catch 块中异常类型
    • Lambda 表达式中函数式接口的类型
    • 方法引用中函数式接口的类型

注意:

  • var 不是一个关键字,而是一个类型名,将它作为变量的类型。不能使用 var 作为类名。

  • 这不是 JavaScript。var 并不会改变 Java 是一门静态类型语言的事实。编译器负责推断出类型,并把结果写入字节码文件,就好像是开发人员自己敲入类型一样。

JDK 13

文本块

现实问题:

在 Java 中,通常需要使用 String 类型表达 HTML,XML,SQL 或 JSON 等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。

JDK13 的新特性

使用"""作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。因此,文本块将提高 Java 程序的可读性和可写性。

基本使用:

java
"""
line1
line2
line3
"""

相当于:

java
"line1\nline2\nline3\n"

或者一个连接的字符串:

java
"line1\n" +
"line2\n" +
"line3\n"

文本块可以表示空字符串,但不建议这样做,因为它需要两行源代码:

java
String empty = """
""";

举例 1:普通文本

原有写法:

java
 String text1 = "The Sound of silence\n" +
                "Hello darkness, my old friend\n" +
                "I've come to talk with you again\n" +
                "Because a vision softly creeping\n" +
                "Left its seeds while I was sleeping\n" +
                "And the vision that was planted in my brain\n" +
                "Still remains\n" +
                "Within the sound of silence";

System.out.println(text1);

使用新特性:

java
String text2 = """
                The Sound of silence
                Hello darkness, my old friend
                I've come to talk with you again
                Because a vision softly creeping
                Left its seeds while I was sleeping
                And the vision that was planted in my brain
                Still remains
                Within the sound of silence
                """;
System.out.println(text2);

举例 2:HTML 语句

html
<html>
  <body>
    <p>Hello, 尚硅谷</p>
  </body>
</html>

将其复制到 Java 的字符串中,会展示成以下内容:

java
"<html>\n" +
"    <body>\n" +
"        <p>Hello, 尚硅谷</p>\n" +
"    </body>\n" +
"</html>\n";

即被自动进行了转义,这样的字符串看起来不是很直观,在 JDK 13 中:

java
"""
<html>
  <body>
      <p>Hello, world</p>
  </body>
</html>
""";

举例 3:SQL 语句

sql
select employee_id,last_name,salary,department_id
from employees
where department_id in (40,50,60)
order by department_id asc

原有方式:

java
String sql = "SELECT id,NAME,email\n" +
                "FROM customers\n" +
                "WHERE id > 4\n" +
                "ORDER BY email DESC";

使用新特性:

java
String sql1 = """
                SELECT id,NAME,email
                FROM customers
                WHERE id > 4
                ORDER BY email DESC
                """;

举例 4:JSON 字符串

原有方式:

java
String myJson = "{\n" +
                "    \"name\":\"Zhang San\",\n" +
                "     \"address\":\"西安市\",\n" +
                "    \"email\":\"zhangsan@aday.fun\"\n" +
                "}";
System.out.println(myJson);

使用新特性:

java
String myJson1 = """
                {
                    "name":"Zhang San",
                     "address":"西安市",
                    "email":"zhangsan@aday.fun"
                }""";
System.out.println(myJson1);

JDK14 中二次预览特性

JDK14 的版本主要增加了两个 escape sequences,分别是\\s,分别表示 取消换行操作(\) 和 表示一个空格(\s)。

java
public class Feature05 {
    //jdk14新特性
    @Test
    public void test5(){
        String sql1 = """
                SELECT id,NAME,email
                FROM customers
                WHERE id > 4
                ORDER BY email DESC
                """;
        System.out.println(sql1);

        // \:取消换行操作
        // \s:表示一个空格
        String sql2 = """
                SELECT id,NAME,email \
                FROM customers\s\
                WHERE id > 4 \
                ORDER BY email DESC
                """;
        System.out.println(sql2);
    }
}

JDK 14

switch 表达式

传统 switch 声明语句的弊端:

  • 匹配是自上而下的,如果忘记写 break,后面的 case 语句不论匹配与否都会执行; --->case 穿透
  • 所有的 case 语句共用一个块范围,在不同的 case 语句定义的变量名不能重复;
  • 不能在一个 case 里写多个执行结果一致的条件;
  • 整个 switch 不能作为表达式返回值;

注意:JDK12 开始为预览特性,JDK 14 成为正式特性。

  • Java 12 将会对 switch 声明语句进行扩展,使用case L ->来替代以前的break;,省去了 break 语句,避免了因少写 break 而出错。

  • 同时将多个 case 合并到一行,显得简洁、清晰,也更加优雅的表达逻辑分支。

  • 为了保持兼容性,case 条件语句中依然可以使用字符 : ,但是同一个 switch 结构里不能混用 -> : ,否则编译错误。

Java 12 中:

java
public class SwitchTest1 {
    public static void main(String[] args) {
        Fruit fruit = Fruit.GRAPE;
        switch(fruit){
            case PEAR -> System.out.println(4);
            case APPLE,MANGO,GRAPE -> System.out.println(5);
            case ORANGE,PAPAYA -> System.out.println(6);
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
    }
}

public class SwitchTest2 {
    public static void main(String[] args) {
        Fruit fruit = Fruit.GRAPE;
        int numberOfLetters = switch(fruit){
            case PEAR -> 4;
            case APPLE,MANGO,GRAPE -> 5;
            case ORANGE,PAPAYA -> 6;
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
        System.out.println(numberOfLetters);
    }
}

JDK13 中二次预览特性:

JDK13 中引入了 yield 语句,用于返回值。这意味着,switch 表达式(返回值)应该使用 yield,switch 语句(不返回值)应该使用 break。

yield 和 return 的区别在于:return 会直接跳出当前循环或者方法,而 yield 只会跳出当前 switch 块。

注意:JDK13 开始为预览特性,JDK 14 成为正式特性。

在 JDK13 中:

java
@Test
public void testSwitch2(){
    String x = "3";
    int i = switch (x) {
        case "1" -> 1;
        case "2" -> 2;
        default -> {
            yield 3;
        }
    };
    System.out.println(i);
}

JDK17 的预览特性:switch 的模式匹配

旧写法:

java
static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

模式匹配新写法:

java
static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
}

直接在 switch 上支持 Object 类型,这就等于同时支持多种类型,使用模式匹配得到具体类型,大大简化了语法量,这个功能很实用。

JDK 16

instanceof 的模式匹配

instanceof 模式匹配通过提供更为简便的语法,来提高生产力。有了该功能,可以减少 Java 程序中显式强制转换的数量,实现更精确、简洁的类型安全的代码。

注意:JDK 14 开始为预览特性,从 JDK 16 开始成为正式特性。

Java 14 之前旧写法:

java
if(obj instanceof String){
    String str = (String)obj; //需要强转
    .. str.contains(..)..
}else{
    ...
}

Java 14 新特性写法:

java
if(obj instanceof String str){
    .. str.contains(..)..
}else{
    ...
}

举例:

java
/**
 * instanceof的模式匹配
 */
public class Feature01 {
    @Test
    public void test1(){

        Object obj = new String("hello,Java14");
        obj = null;//在使用null 匹配instanceof 时,返回都是false.
        if(obj instanceof String){
            String str = (String) obj;
            System.out.println(str.contains("Java"));
        }else{
            System.out.println("非String类型");
        }

        //举例1:
        if(obj instanceof String str){ //新特性:省去了强制类型转换的过程
            System.out.println(str.contains("Java"));
        }else{
            System.out.println("非String类型");
        }
    }
}

Record

早在 2019 年 2 月份,Java 语言架构师 Brian Goetz,曾写文抱怨“Java太啰嗦”或有太多的“繁文缛节”。他提到:开发人员想要创建纯数据载体类(plain data carriers)通常都必须编写大量低价值、重复的、容易出错的代码。如:构造函数、getter/setter、equals()、hashCode()以及 toString()等。

以至于很多人选择使用 IDE 的功能来自动生成这些代码。还有一些开发会选择使用一些第三方类库,如 Lombok 等来生成这些方法。

**JDK14 中预览特性:神说要用 record,于是就有了。**实现一个简单的数据载体类,为了避免编写:构造函数,访问器,equals(),hashCode () ,toString ()等,Java 14 推出 record。

注意:JDK 14 预览特性,JDK 16 成为正式特性。

record 是一种全新的类型,它本质上是一个 final 类,同时所有的属性都是 final 修饰,它会自动编译出 public gethashcodeequalstoString、构造器等结构,减少了代码编写量。

具体来说:当你用record 声明一个类时,该类将自动拥有以下功能:

  • 获取成员变量的简单方法,比如例题中的 name() 和 partner() 。注意区别于我们平常 getter()的写法。
  • 一个 equals 方法的实现,执行比较时会比较该类的所有成员属性。
  • 重写 hashCode() 方法。
  • 一个可以打印该类所有成员属性的 toString() 方法。
  • 只有一个构造方法。

此外:

  • 还可以在 record 声明的类中定义静态字段、静态方法、构造器或实例方法。

  • 不能在 record 声明的类中定义实例字段;类不能声明为 abstract;不能声明显式的父类等。

举例 1(旧写法):

java
class Point {
    private final int x;
    private final int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    int x() {
        return x;
    }

    int y() {
        return y;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return other.x == x && other.y == y;
    }

    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

举例 1(新写法):

java
record Point(int x, int y) { }



public record Dog(String name, Integer age) {}

public class Java14Record {

    public static void main(String[] args) {
        Dog dog1 = new Dog("牧羊犬", 1);
        Dog dog2 = new Dog("田园犬", 2);
        Dog dog3 = new Dog("哈士奇", 3);
        System.out.println(dog1);
        System.out.println(dog2);
        System.out.println(dog3);
    }
}

举例 2:

java
public class Feature07 {
    @Test
    public void test1(){
        //测试构造器
        Person p1 = new Person("罗密欧",new Person("zhuliye",null));
        //测试toString()
        System.out.println(p1);
        //测试equals():
        Person p2 = new Person("罗密欧",new Person("zhuliye",null));
        System.out.println(p1.equals(p2));

        //测试hashCode()和equals()
        HashSet<Person> set = new HashSet<>();
        set.add(p1);
        set.add(p2);

        for (Person person : set) {
            System.out.println(person);
        }

        //测试name()和partner():类似于getName()和getPartner()
        System.out.println(p1.name());
        System.out.println(p1.partner());

    }

    @Test
    public void test2(){
        Person p1 = new Person("zhuyingtai");

        System.out.println(p1.getNameInUpperCase());

        Person.nation = "CHN";
        System.out.println(Person.showNation());

    }
}
java
public record Person(String name,Person partner) {
    //还可以声明静态的属性、静态的方法、构造器、实例方法
    public static String nation;

    public static String showNation(){
        return nation;
    }

    public Person(String name){
        this(name,null);
    }

    public String getNameInUpperCase(){
        return name.toUpperCase();
    }
    //不可以声明非静态的属性
//    private int id;//报错
}

//不可以将record定义的类声明为abstract的
//abstract record Order(){
//
//}

//不可以给record定义的类声明显式的父类(非Record类)
//record Order() extends Thread{
//
//}

记录不适合哪些场景

record 的设计目标是提供一种将数据建模为数据的好方法。它也不是 JavaBeans 的直接替代品,因为 record 的方法不符合 JavaBeans 的 get 标准。另外 JavaBeans 通常是可变的,而记录是不可变的。尽管它们的用途有点像,但记录并不会以某种方式取代 JavaBean。

JDK 17

密封类 sealed

在 Java 中如果想让一个类不能被继承和修改,这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承,要么不能继承的机制不够灵活,有些时候我们可能想让某个类可以被某些类型继承,但是又不能随意继承,是做不到的。Java 15 尝试解决这个问题,引入了 sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。

注意:JDK15 的预览特性,JDK 17 成为正式特性。

通过密封的类和接口来限制超类的使用,密封的类和接口限制其它可能继承或实现它们的其它类或接口。

具体使用:

  • 使用修饰符sealed,可以将一个类声明为密封类。密封的类使用保留关键字permits列出可以直接扩展(即 extends)它的类。

  • sealed 修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是 finalsealednon-sealed 三者之一。

举例:

java
public abstract sealed class Shape permits Circle, Rectangle, Square {...}

public final class Circle extends Shape {...} //final表示Circle不能再被继承了

public sealed class Rectangle extends Shape permits TransparentRectangle, FilledRectangle {...}

public final class TransparentRectangle extends Rectangle {...}

public final class FilledRectangle extends Rectangle {...}

public non-sealed class Square extends Shape {...} //non-sealed表示可以允许任何类继承

JDK9 ~ 17 Optional 类的更新

新增方法描述新增的版本
boolean isEmpty()判断 value 是否为空JDK 11
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)value 非空,执行参数 1 功能;如果 value 为空,执行参数 2 功能JDK 9
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)value 非空,返回对应的 Optional;value 为空,返回形参封装的 OptionalJDK 9
Stream<T> stream()value 非空,返回仅包含此 value 的 Stream;否则,返回一个空的 StreamJDK 9
T orElseThrow()value 非空,返回 value;否则抛异常 NoSuchElementExceptionJDK 10

String 存储结构和 API 变更

这是 JDK9 的新特性。

产生背景:

Motivation The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.

使用说明:

Description

We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.

结论:String 再也不用 char[] 来存储啦,改成了 byte[] 加上编码标记,节约了一些空间。

java
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
	...
}

拓展:StringBuffer 与 StringBuilder

那 StringBuffer 和 StringBuilder 是否仍无动于衷呢?

String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM's intrinsic string operations.

JDK11 新特性:新增了一系列字符串处理方法

描述举例
判断字符串是否为空白" ".isBlank(); // true
去除首尾空白" Javastack ".strip(); // "Javastack"
去除尾部空格" Javastack ".stripTrailing(); // " Javastack"
去除首部空格" Javastack ".stripLeading(); // "Javastack "
复制字符串"Java".repeat(3);// "JavaJavaJava"
行数统计"A\nB\nC".lines().count(); // 3

JDK12 新特性:String 实现了 Constable 接口

String 源码:

java
public final class String implements java.io.Serializable, Comparable<String>, CharSequence,Constable, ConstantDesc {

java.lang.constant.Constable 接口定义了抽象方法:

java
public interface Constable {
	Optional<? extends ConstantDesc> describeConstable();
}

Java 12 String 的实现源码:

java
/**
 * Returns an {@link Optional} containing the nominal descriptor for this
 * instance, which is the instance itself.
 *
 * @return an {@link Optional} describing the {@linkplain String} instance
 * @since 12
 */
@Override
public Optional<String> describeConstable() {
	return Optional.of(this);
}

很简单,其实就是调用 Optional.of 方法返回一个 Optional 类型。

举例:

java
private static void testDescribeConstable() {
	String name = "Java高级工程师";
	Optional<String> optional = name.describeConstable();
	System.out.println(optional.get());
}

结果输出:

Java高级工程师

JDK12 新特性:String 新增方法

String 的 transform(Function)

java
var result = "foo".transform(input -> input + " bar");
System.out.println(result); //foo bar

或者

java
var result = "foo".transform(input -> input + " bar").transform(String::toUpperCase)
System.out.println(result); //FOO BAR

对应的源码:

java
/**
* This method allows the application of a function to {@code this}
* string. The function should expect a single String argument
* and produce an {@code R} result.
* @since 12
*/
public <R> R transform(Function<? super String, ? extends R> f) {
 return f.apply(this);
}

在某种情况下,该方法应该被称为 map()。

举例:

java
private static void testTransform() {
	System.out.println("======test java 12 transform======");
	List<String> list1 = List.of("Java", " Python", " C++ ");
	List<String> list2 = new ArrayList<>();
	list1.forEach(element -> list2.add(element.transform(String::strip)
								  .transform(String::toUpperCase)
								  .transform((e) -> "Hi," + e))
				 );
	list2.forEach(System.out::println);
}

结果输出:

java
======test java 12 transform======
Hi,JAVA
Hi,PYTHON
Hi,C++

如果使用 Java 8 的 Stream 特性,可以如下实现:

java
private static void testTransform1() {
        System.out.println("======test before java 12 ======");
        List<String> list1 = List.of("Java  ", " Python", " C++ ");

        Stream<String> stringStream = list1.stream().map(element -> element.strip()).map(String::toUpperCase).map(element -> "Hello," + element);
        List<String> list2 = stringStream.collect(Collectors.toList());
        list2.forEach(System.out::println);
    }

JDK17:标记删除 Applet API

Applet API 提供了一种将 Java AWT/Swing 控件嵌入到浏览器网页中的方法。不过,目前 Applet 已经被淘汰。大部分人可能压根就没有用过 Applet。

Applet API 实际上是无用的,因为所有 Web 浏览器供应商都已删除或透露计划放弃对 Java 浏览器插件的支持。Java 9 的时候,Applet API 已经被标记为过时,Java 17 的时候终于标记为删除了。

javac 编译的变化

java
// 编译
javac JavaStack.java

// 运行
java JavaStack

我们的认知里,要运行一个 Java 源代码必须先编译,再运行。而在 Java 11 版本中,通过一个 java 命令就直接搞定了,如下所示:

java JavaStack.java

注意点:

  • 执行源文件中的第一个类,第一个类必须包含主方法。

GC 方面新特性

GC 是 Java 主要优势之一。 然而,当 GC 停顿太长,就会开始影响应用的响应时间。随着现代系统中内存不断增长,用户和程序员希望 JVM 能够以高效的方式充分利用这些内存, 并且无需长时间的 GC 暂停时间。

8.3.1 G1 GC

JDK9 以后默认的垃圾回收器是 G1 GC。

JDK10 : 为 G1 提供并行的 Full GC

G1 最大的亮点就是可以尽量的避免 full gc。但毕竟是“尽量”,在有些情况下,G1 就要进行 full gc 了,比如如果它无法足够快的回收内存的时候,它就会强制停止所有的应用线程然后清理。

在 Java10 之前,一个单线程版的标记-清除-压缩算法被用于 full gc。为了尽量减少 full gc 带来的影响,在 Java10 中,就把之前的那个单线程版的标记-清除-压缩的 full gc 算法改成了支持多个线程同时 full gc。这样也算是减少了 full gc 所带来的停顿,从而提高性能。

你可以通过-XX:ParallelGCThreads参数来指定用于并行 GC 的线程数。

JDK12:可中断的 G1 Mixed GC

JDK12:增强 G1,自动返回未用堆内存给操作系统

8.3.2 Shenandoah GC

JDK12:Shenandoah GC:低停顿时间的 GC Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目 Pauseless GC 的实现,旨在针对 JVM 上的内存收回实现低停顿的需求

据 Red Hat 研发 Shenandoah 团队对外宣称,Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。

Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等。

这是一个实验性功能,不包含在默认(Oracle)的 OpenJDK 版本中。

JDK15:Shenandoah 垃圾回收算法转正

Shenandoah 垃圾回收算法终于从实验特性转变为产品特性,这是一个从 JDK 12 引入的回收算法,该算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah 的暂停时间与堆大小无关,无论堆栈是 200 MB 还是 200 GB,都具有相同的一致暂停时间。

Shenandoah 在 JDK12 被作为 experimental 引入,在 JDK15 变为 Production;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC来启用,现在只需要-XX:+UseShenandoahGC即可启用

8.3.3 革命性的 ZGC

JDK11:引入革命性的 ZGC

ZGC,这应该是 JDK11 最为瞩目的特性,没有之一。

ZGC 是一个并发、基于 region、压缩型的垃圾收集器。

ZGC 的设计目标是:支持 TB 级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于 15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于 DRAM 和冷对象置于 NVMe 闪存),或压缩堆。

JDK13:ZGC:将未使用的堆内存归还给操作系统

JDK14:ZGC on macOS 和 windows

  • JDK14 之前,ZGC 仅 Linux 才支持。现在 mac 或 Windows 上也能使用 ZGC 了,示例如下:

    -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
  • ZGC 与 Shenandoah 目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。

JDK15:ZGC 功能转正

ZGC 是 Java 11 引入的新的垃圾收集器,经过了多个实验阶段,自此终于成为正式特性。

但是这并不是替换默认的 GC,默认的 GC 仍然还是 G1;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseZGC来启用 ZGC,现在只需要-XX:+UseZGC就可以。相信不久的将来它必将成为默认的垃圾回收器。

ZGC 的性能已经相当亮眼,用“令人震惊、革命性”来形容,不为过。未来将成为服务端、大内存、低延迟应用的首选垃圾收集器。

怎么形容 Shenandoah 和 ZGC 的关系呢?异同点大概如下:

  • 相同点:性能几乎可认为是相同的
  • 不同点:ZGC 是 Oracle JDK 的,根正苗红。而 Shenandoah 只存在于 OpenJDK 中,因此使用时需注意你的 JDK 版本

JDK16:ZGC 并发线程处理

在线程的堆栈处理过程中,总有一个制约因素就是 safepoints。在 safepoints 这个点,Java 的线程是要暂停执行的,从而限制了 GC 的效率。

回顾:

我们都知道,在之前,需要 GC 的时候,为了进行垃圾回收,需要所有的线程都暂停下来,这个暂停的时间我们称为 Stop The World

而为了实现 STW 这个操作, JVM 需要为每个线程选择一个点停止运行,这个点就叫做安全点(Safepoints)

而 ZGC 的并发线程堆栈处理可以保证 Java 线程可以在 GC safepoints 的同时可以并发执行。它有助于提高所开发的 Java 软件应用程序的性能和效率。