Java本身是一种面向对象的语言,最显著的特性有两个方面,一是所谓的“一次编译,到处执行”(Compile once, run anywhere),能够非常容易地获得跨平台能力;另外就是垃圾收集(GC, Garbage Collection),Java通过垃圾收集器(Garbage Collector)回收分配内存,大部分情况下,程序员不需要自己操心内存的分配和回收。
我们日常会接触到 JRE(Java Runtime Environment)或者 JDK(Java Development Kit)。 JRE,也就是 Java运行环境,包含了 JVM和 Java类库,以及一些模块等。而 JDK可以看作是 JRE的一个超集,提供了更多工具,比如编译器、各种诊断工具等。对于“Java是解释执行”这句话,这个说法不太准确。我们开发的 Java的源代码,首先通过 Javac编译成为字节码(bytecode),然后,在运行时,通过 Java虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。但是常见的 JVM,比如我们大多数情况使用的 Oracle JDK提供的 Hospot JVM,都提供了 JIT(Just-In-Time)编译器,也就是通常所说的动态编译器,JIT能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。
众所周知,我们通常把 Java分为编译期和运行时。这里说的 Java的编译和 C/C++是有着不同的意义的,Javac的编译,编译 Java源码生成“.class”文件里面实际是字节码,而不是可以直接执行的机器码。Java通过字节码和 Java虚拟机(JVM)这种跨平台的抽象,屏蔽了操作系统和硬件的细节,这也是实现“一次编译,到处执行”的基础。
Java虚拟机启动时,可以指定不同的参数对运行模式进行选择。 比如,指定“-Xint”,就是告诉 JVM只进行解释执行,不对代码进行编译,这种模式抛弃了 JIT可能带来的性能优势。毕竟解释器(interpreter)是逐条读入,逐条解释运行的。与其相对应的,还有一个“-Xcomp”参数,这是告诉 JVM关闭解释器,不要进行解释执行,或者叫作最大优化级别。那你可能会问这种模式是不是最高效啊?简单说,还真未必。“-Xcomp”会导致 JVM启动变慢非常多,同时有些 JIT编译器优化方式,比如分支预测,如果不进行 profiling,往往并不能进行有效优化。
除了我们日常最常见的 Java使用模式,其实还有一种新的编译方式,即所谓的 AOT(Ahead-of-Time Compilation),直接将字节码编译成机器代码,这样就避免了 JIT预热等各方面的开销,比如 Oracle JDK 9就引入了实验性的 AOT特性,并且增加了新的 jaotc工具。利用下面的命令把某个类或者某个模块编译成为 AOT库。
jaotc --output libHelloWorld.so HelloWorld.class
jaotc --output libjava.base.so --module java.base
然后,在启动时直接指定就可以了。
java -XX:AOTLibrary=./libHelloWorld.so,./libjava.base.so HelloWorld
而且,Oracle JDK支持分层编译和 AOT协作使用,这两者并不是二选一的关系。如果你有兴趣,可以参考相关文档:http://openjdk.java.net/jeps/295。AOT也不仅仅是只有这一种方式,业界早就有第三方工具(如 GCJ、Excelsior JET)提供相关功能。
另外,JVM作为一个强大的平台,不仅仅只有 Java语言可以运行在 JVM上,本质上合规的字节码都可以运行,Java语言自身也为此提供了便利,我们可以看到类似 Clojure、Scala、Groovy、JRuby、Jython等大量 JVM语言,活跃在不同的场景。
回归正题,对于 Java平台的理解,可以从很多方面简明扼要地谈一下,例如:Java语言特性,包括泛型、Lambda等语言特性;基础类库,包括集合、IO/NIO、网络、并发、安全等基础类库。对于我们日常工作应用较多的类库,面试前可以系统化总结一下,有助于临场发挥。
或者谈谈 JVM的一些基础概念和机制,比如 Java的类加载机制,常用版本 JDK(如 JDK 8)内嵌的 Class-Loader,例如 Bootstrap、 Application和 Extension Class-loader;类加载大致过程:加载、验证、链接、初始化(这里参考了周志明的《深入理解 Java虚拟机》,非常棒的 JVM上手书籍);自定义 Class-Loader等。还有垃圾收集的基本原理,最常见的垃圾收集器,如 SerialGC、Parallel GC、 CMS、 G1等,对于适用于什么样的工作负载最好也心里有数。这些都是可以扩展开的领域,我会在后面的专栏对此进行更系统的介绍。
当然还有 JDK包含哪些工具或者 Java领域内其他工具等,如编译器、运行时环境、安全工具、诊断和监控工具等。这些基本工具是日常工作效率的保证,对于我们工作在其他语言平台上,同样有所帮助,很多都是触类旁通的。
在面向对象的Collection中处处体现泛型和多态的思想,且慢,由于泛型和多态两者之间本身就有一定的相似性,让我们先认清楚泛型和多态之后再进入话题。
多态是面向对象最基本的概念之一,即要模糊父类和之类之间的区别,如何模糊呢?我们再来举网络上的那个例子:
class Aninal{ 叫;}//叫是动物的一个方法
class 猫 extend Animal{叫;}
class 狗 extend Animal{叫;}
Animal 狗 = new 狗();//这里用Animal来定义狗,没用用狗来定义,真正是狗叫还是猫叫,是
Animal 猫 = new 猫();//看赋予它的对象,这样真正实现了接口重用,一个对象可以调用
狗.叫();//Animal让它叫,而具体是谁在叫,到时候就看赋予它的子类是哪一个了,同时还有于
猫.叫();//扩展,想象一下,当添加了鸡之后,前面调用Animal的对象的方法都不用做任何修改。
具体在Java中的Collection的Iterator是运用多态最典型的例子,想象一下,Collection中有么多类(ArrayList,LinkedList,HashSet,TreeSet等),如果对应于每个类都写一个Iterarot,大家应该会知道这会有多臃肿了。但让Collection继承自Iterator(他们都属于接口),其他这些类也都继承自Iterator。还是举个例子:
Collection books = new HashSet();
books.add(15);
Iterator it = books.iterator();//在定义it时用接口Iterator,但在初始化的时候用的却是 while(it.hasNext()) //HashSet类中实现的Iteratr()方法,即完全看子类赋予自己
{ //的是什么,这样就可以操作books中的对象。同时便于扩展
int book = (int)it.next();
System.out.println(book);
}
那我们知道了多态的概念之后,再来看泛型。List
说了这么多,那我们来看看泛型和多态的关系。今天看到网上有个人说:泛型就是多态的一种特例,用多态即可实现泛型。乍一想好像挺有道理的,T可以根据传入的类型来确定其行为,这不就是多态吗?但又一想,多态是在继承层面上的,即根据实际运行时候来确定具体的实现,而泛型是当我们使用这个泛型类时候再去确定这个类里面的成员具体什么类型的,两者不是一个层次的,更不用提用多态实现泛型了。至于具体的实现大家可以自己去查查。
微观角度:
Java平台中有两大核心:
1. Java语言本身、JDK中所提供的核心类库和相关工具
2. Java虚拟机以及其他包含的GC
1. Java语言本身、JDK中所提供的核心类库和相关工具
从事Java平台的开发,掌握Java语言、核心类库以及相关工具是必须的,我觉得这是基础中的基础。
>> 对语言本身的了解,需要开发者非常熟悉语言的语法结构;而Java又是一种面对对象的语言,这又需要开发者深入了解面对对象的设计理念;
>> Java核心类库包含集合类、线程相关类、IO、NIO、J.U.C并发包等;
>> JDK提供的工具包含:基本的编译工具、虚拟机性能检测相关工具等。
2. Java虚拟机
Java语言具有跨平台的特性,也正是因为虚拟机的存在。Java源文件被编译成字节码,被虚拟机加载后执行。这里隐含的意思有两层:
1)大部分情况下,编程者只需要关心Java语言本身,而无需特意关心底层细节。包括对内存的分配和回收,也全权交给了GC。
2)对于虚拟机而言,只要是符合规范的字节码,它们都能被加载执行,当然,能正常运行的程序光满足这点是不行的,程序本身需要保证在运行时不出现异常。所以,Scala、Kotlin、Jython等语言也可以跑在虚拟机上。
围绕虚拟机的效率问题展开,将涉及到一些优化技术,例如:JIT、AOT。因为如果虚拟机加载字节码后,完全进行解释执行,这势必会影响执行效率。所以,对于这个运行环节,虚拟机会进行一些优化处理,例如JIT技术,会将某些运行特别频繁的代码编译成机器码。而AOT技术,是在运行前,通过工具直接将字节码转换为机器码。