谈谈我对Java平台的理解

Write once,run anywhere是Java诞生之初的Slogan。
Java是一个平台,除了Java语言本身的特性,还有Java类库,Java虚拟机,工具和庞大的生态。

Java平台的组成部分

Java号称”一次编写,到处运行”,这个特性主要依托的是各个平台的JVM虚拟机实现。
开发者编写源代码,通过编译器将源代码编译成平台无关的.CLASS字节码文件。
将字节码交给不同平台的JVM虚拟机执行,实现跨平台特性。

Java语言

学一门语言,不仅仅是学习语言的语法,更重要的是学习这门语言的编程范式。

面向对象是Java语言的编程范式。除了基本数据类型以外,其他所有的类都继承自Object类。
Java以类进行组织代码,以实例化对象的方式进行实例方法调用。
Java已经把面向对象玩到炉火纯青,在工程实践领域人们总结出了金典的23种设计模式。
设计模式并不是什么圣经,严格意义上来说,我认为设计模式是对语言本身缺陷的一种补充。
比如工厂模式,在Java中实现 Duck Type,需要通过类实现接口方法的方式进行实现:
Calculate.java

1
2
3
4
5
package com.lanshiqin.duck;

public interface Calculate {
int calculate(int num);
}

CalculateOneImpl.java

1
2
3
4
5
6
7
8
package com.lanshiqin.duck;

public class CalculateOneImpl implements Calculate {
@Override
public int calculate(int num) {
return num;
}
}

CalculateTwoImpl.java

1
2
3
4
5
6
7
8
package com.lanshiqin.duck;

public class CalculateTwoImpl implements Calculate {
@Override
public int calculate(int num) {
return num*num;
}
}

CalculateThreeImpl.java

1
2
3
4
5
6
public class CalculateThreeImpl implements Calculate {
@Override
public int calculate(int num) {
return num * num * num;
}
}

CalculateSample.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.lanshiqin.duck;

import java.util.HashMap;
import java.util.Map;

public class CalculateSample {
public static void main(String[] args) {
Map<Integer, Object> map = new HashMap<>();
map.put(1, new CalculateOneImpl());
map.put(2, new CalculateTwoImpl());
map.put(3, new CalculateThreeImpl());

// 通过传给Map不同参数,调用不同对象的方法
System.out.println(((Calculate) map.get(1)).calculate(2));
System.out.println(((Calculate) map.get(2)).calculate(2));
System.out.println(((Calculate) map.get(3)).calculate(2));
}
}

而在Go语言中 Map 的value可以是一个方法
Go的Duck Type可以方便的实现单一方法对象的工厂模式。
map_ext_test.go

1
2
3
4
5
6
7
8
9
10
11
12
package map_ext

import "testing"

func TestDuckTypeWithMap(t *testing.T) {
m := map[int]func(op int) int{}
m[1] = func(op int) int { return op }
m[2] = func(op int) int { return op * op }
m[3] = func(op int) int { return op * op * op }
// 通过传给Map不同参数,调用不同的方法
t.Log(m[1](2), m[2](2), m[3](2))
}

Duck Type并不是动态语言的专利,Go既可以支持动态语言的各种特性,比如类型推断,多返回值,也支持编译型语言的特性可以直接编译成可执行文件。
严格来说,Java并没有很好的支持Duck Type这种写法,Java语言的语法本身也是在不断的进化,比如JDK8引入类Lambda就是借鉴了函数式的编程范式,以及JDK9开始引入的自动类型推断等,越来越多的特性被引入到语法糖中,Java语言本身也是在不断的进化中。

如果把学习一门编程语言简单的分为三个层次的话,第一层就是了解这个语言的语法规则,与语言提供的能力。第二层就是了解这个语言的适用范围和生态,与其他语言相比有哪些优劣。
第三层就是了解底层的运行机制,当程序出现罕见问题的时候能够在更底层的视角解决问题,进行相应的调优和故障排查。

Java的生态

JVM屏蔽了不同平台底层的硬件差异和操作系统的API差异,提供了统一的运行环境。
所以,只要符合JVM虚拟机字节码规范的字节码文件,都能运行在JVM虚拟机上。
除了Java语言本身以外,还有其他基于JVM的语言,比如Kotlin、Scala、Groovy等。
这些基于JVM的语言实现了自己的语法,最终通过编译器都编译成了JVM可以执行都字节码。
如果学过编译原理,你也可以重写编译器,实现一门自己的脚本语言,生成符合规范的字节码文件,就可以脱离Java语言本身的语法规则限制,依托于JVM的生态实现强大的功能。
在Java生态领域,已经不仅仅是Java语言本身了,比如知名的Elastic Search,就实现了DSL。
每当有个语言新秀想要取缔Java的霸主地位,并号称自己是”世界上最好的语言”的时候,最后却发现无法完全取代,因为Java的生态太庞大了,比如大数据的相关技术 Flink和Spark等。
每个语言都有自己的优劣,比如Java的JVM在云原生时代就显得不那么主流了,Java本身也在寻找变革和自己的方向,并且在多个方向做尝试。

Java到底是解释执行,还是编译执行?

“一次编写,到处运行”是Java语言的特性,这一重要特性依靠的是JVM虚拟机。

JVM虚拟机通过读入字节码,将字节码解释成平台机器码进行执行。

那Java是解释执行吗?

不全是。也可以编译执行,直接执行机器码。 比如JIT即时编译技术,当一个方法被执行一定次数之后,就会被JIT编译成对应平台的机器码,下次执行时直接执行的就是平台机器码,不再是解释执行,前提条件是应用启动后需要进行成千上万次的预热。

那有没有不需要预热,直接就可以将Java代码编译成平台机器码的?

有。比如在JDK9中加入的实验性特性AOT,可以提前把字节码编译成平台字节码,但是是以牺牲启动速度为代价的。

编译成二进制执行,JDK9的实验性特性并不是唯一的途径,而且在JDK16中AOT相关工具已经被移除 JEP 410: Remove the Experimental AOT and JIT Compiler。官方给的解释是用的人不多,维护AOT功能需要成本。
对于JIT和AOT来说,有更好的解决方案,就是GraalVm

云原生时代,Java如何破局?

Java的一次编写,到处运行的优势,已经被容器技术削弱。

云原生与Java的很多理念相悖。如果说微服务Java依托Spring Boot还能大行其道,那云原生就显得力不从心了。
云原生很重要的一点就是原生。推崇把程序编译成机器平台原生可执行的机器码,而不是通过虚拟机运行。
比如Serverless概念的Faas落地技术,函数即服务。推崇k8s在瞬间拉起N个服务执行,执行完成后关闭多余资源。
而Java这种依赖于JVM虚拟机的方式在快速启动和部署就没有优势了,而且包体积相对于其他原生语言来说也更加大和臃肿。
云原生的头牌语言是Go,可以编译成对应平台的机器码,外部依赖在编译时就已经构建好了,扔到服务器上就能直接运行。在资源节省上是Java所不能不及的。
Java依赖JVM动态生成字节码的技术,以及其他的很多黑魔法在云原生概念下都没有优势。

如果Java去除了JVM虚拟机,那依托的JVM生态就很大一部分不可用。
并不是所有Java类库都能通过AOT提前编译成机器码的,比如Spring以及一些工具使用了动态字节码的技术在运行时修改的黑魔法就不能使用。

云原生时代,Java也在寻找变革,并且提出了实践项目

有关云原生的思考,推荐周志明的云原生时代,Java 的危与机

0%