开发过程
我们利用JDK(调用JAVA API)开发了属于我们自己的JAVA程序后,通过JDK中的编译程序(javac)将我们的文本java文件编译成JAVA字节码,在JRE上运行这些JAVA字节码,JVM解析这些字节码,映射到CPU指令集或OS的系统调用。
java虚拟机:Java Virtual Machine
是用来解释并执行java源代码文件的,然后实现跨平台。
计算机仅能识别01信号,通过01的组合产生了不同的机器指令,这些机器指令是离CPU指令集最近的编码,是CPU可以直接解读的指令,在不同的时代,不同的厂商,机器指令是不同的。为了避免针对不同的硬件平台去编写多套代码,所以增加了一个中间层来解决这个问题,这个中间层就是虚拟机,虚拟机将字节码(字节码是Java编译器编译得到的)进行解释执行,屏蔽对底层操作系统的依赖,也可以将字节码进行编译执行,如果是热点代码,会通过JIT动态地编译为机器码,提高执行效率。
JDK包含JRE,JRE包含JVM。
JVM是JRE的一部分,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用。
JRE是java运行时环境,也就是我们说的JAVA平台,所有的Java程序都要在JRE下才能运行。包括JVM和JAVA核心类库和支持文件。与JDK相比,它不包含开发工具——编译器、调试器和其它工具。
JDK是整个JAVA的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。
最主流的JDK是Sun公司发布的JDK,除了Sun之外,还有很多公司和组织都开发了属于自己的JDK,例如国外IBM公司开发了属于自己的JDK,阿里巴巴也开发了属于自己的AJDK,各个组织开发自己的JDK都是为了在某些方面得到一些提高,以适应自己的需求,比如IBM的JDK据说运行效率就比SUN的JDK高的多。
我们常常用JDK来代指Java API,Java API是Java的应用程序接口,其实就是前辈们写好的一些java Class,包括一些重要的语言结构以及基本图形,网络和文件I/O等等 ,我们在自己的程序中,调用前辈们写好的这些Class,来作为我们自己开发的一个基础。当然,现在已经有越来越多的性能更好或者功能更强大的第三方类库供我们使用。
CPU架构
ARM和X86
RISC(ARM)和CISC(x86)是设计制造微处理器的两种典型技术,虽然它们都是试图在体系结构、操作运行、软件硬件、编译时间和运行时间等诸多因素中做出某种平衡,以求达到高效的目的,但采用的方法不同,因此,在很多方面差异很大。
x86性能高 ,扩展性好,兼容性强,但是功耗高。
ARM在完成综合性工作方面根本就处于劣势,而在一些任务相对固定的应用场合其优势就能发挥得淋漓尽致。奉行“够用就好”的原则,功耗低,手持式移动终端领域占据优势。
CPU架构详解
Ubuntn是Linux的发行版之一。
BIOS
基本输入输出系统,Basic Input Output System,它是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序,它可从CMOS(Complementary Metal Oxide Semiconductor(互补金属氧化物半导体))中读写系统设置的具体信息。
CMOS
它是指制造大规模集成电路芯片用的一种技术或用这种技术制造出来的芯片,是电脑主板上的一块可读写的RAM芯片。因为可读写的特性,所以在电脑主板上用来保存BIOS设置完电脑硬件参数后的数据,这个芯片仅仅是用来存放数据的。
电压控制的一种放大器件,是组成CMOS数字集成电路的基本单元。
而对BIOS中各项参数的设定要通过专门的程序。BIOS设置程序一般都被厂商整合在芯片中,在开机时通过特定的按键就可进入BIOS设置程序,方便地对系统进行设置。因此BIOS设置有时也被叫做CMOS设置。
源码转换为字节码
java源文件–>词法解析–>语法解析—>语义分析---->生成字节码------>字节码
词法解析是通过空格分隔出单词、操作符、控制符等信息,将其形成token信息流,传递给语法解析器;在语法解析时,把词法解析得到的token信息流按照java语法规则组装成一颗语法树。在语义分析阶段,需要检查关键字的使用是否合理,类型是否匹配,作用域是否正确等。当语义分析完成之后,即可生成字节码。
类加载过程
字节码必须通过类加载过程加载到JVM环境后,才可以执行。
冯诺依曼定义的计算机模型中,任何程序都需要加载到内存中才能与CPU进行交流。字节码文件也同样需要加载到内容中,才可以实例化类。
java的类加载器是一个运行时核心基础设施模块,主要是在启动之初进行类的Load,Link,Init,即加载,链接,初始化。
第一步:Load阶段读取类文件产生的二进制流,并转化为特定的数据结构,初步校验cafe babe魔法数(标识该文件是java文件)、常量池、文件长度、是否有父类等,然后创建对应类的java.lang.Class实例(Class类对类的抽象,Class类的每个实例则代表运行中的一个类)。
第二步:Link阶段包括验证、准备、解析三个步骤。验证是更为详细的验证,验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。比如final是否符合规范,类型是否正确、静态变量是否合理等。准备阶段是为静态变量分配内存,并设定默认值,这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局。
第三步,Init阶段执行类构造器<clinit>
方法,如果赋值运算是通过其他类的静态方法来完成的,那么会马上解析另外一个类,在虚拟机栈中执行完毕后通过返回值进行赋值。这里的初始化才是显式赋值。
深入理解JVM类加载机制
类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段不一定,他有可能是在初始化阶段之后进行,是为了支持java语言运行时绑定。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
这里简要说明下Java中的绑定:绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来,对java来说,绑定分为静态绑定和动态绑定:
静态绑定:即前期绑定。在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。针对java,简单的可以理解为程序编译期的绑定。java当中的方法只有final,static,private和构造方法是前期绑定的。
动态绑定:即晚期绑定,也叫运行时绑定。在运行时根据具体对象的类型进行绑定。在java中,几乎所有的方法都是后期绑定的。
对于任意一个类,都由加载它的类加载器以及这个类本身确定了它在虚拟机中的唯一性。针对同一个.class文件,如果加载这个文件的类加载器不同,那么得到的两个类就不相等。这里的“相等”包括了代表类的Class对象的equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用instanceof关键字对对象所属关系的判定结果。
类加载器的分类:
启动类加载器:Bootstrap ClassLoader,启动类加载器是无法被Java程序直接引用的,负责装载最核心的Java类,比如Object,System,String等。
扩展类加载器:Extension ClassLoader如javax.*开头的类,开发者可以直接使用扩展类加载器。JDK9以后称为平台类加载器Platform ClassLoader。
应用程序类加载器:它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。
因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:
1)隔离加载类。在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境。比如,阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包。
2)修改类的加载方式。除了Bootstrap外,类的加载模型并非强制,其他加载并非一定要引入,或者根据实际情况在某个时间点进行按需进行动态加载。
3)扩展加载源。比如从数据库和网络或者是电视机机顶盒进行加载。
4)防止源码泄露。java代码容易被编译和篡改,可以进行编译加密。那么类加载器也需要自定义,还原加密的字节码。
中间件
由于中间件一般都有自己所依赖的jar包,在同一个工程内引入多个框架时,往往会被迫进行类的仲裁。按照某种规则jar包的版本被统一指定,导致某些类存在包路径、类名相同的情况,就会引起类冲突,导致应用程序出现异常。主流的容器框架都会自定义类加载器,实现不同中间件的类隔离,有效避免了类冲突。
中间件:就是处理我们数据间交互,连接数据分离后两个系统间的通信,中间件不属于任何一个开发项目,就是让我们对应系统间或者数据库间数据流通无感知。
比如:远程过程调用和对象访问中间件,主要解决分布式环境下应用的相互访问问题,这也是支撑应用服务化功能的基础。
消息中间件:解决应用之间的消息传递,解耦,异步等问题
ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。
数据访问中间件:主要解决应用访问数据库的共性问题的组件。比如使用的数据库驱动。例如:ODBC JDBC,以 JDBC 为例,数据库本地维护了一个数据访问中间件,我们在访问数据库的时候,配置的地址其实是直接连接到JDBC这个数据访问中间件,如果我们执行查询数据,或者对数据库的操作都是通过JDBC来连接数据库,然后通过JDBC查询完成数据库以后再返回给我们应用程序。作为中间件,查询过程对于我们是不可知的。
双亲委派模型的工作流程是
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
使用双亲委派模型来组织类加载器之间的关系,有一个很明显的好处,就是Java类随着它的类加载器(说白了,就是它所在的目录)一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。例如,类java.lang.Object类存放在JDK\jre\lib下的rt.jar之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了Object类在程序中的各种类加载器中都是同一个类。
有关初始化赋值
对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。
对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。
对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。
如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。