Johnson Hang

  • ——————————————————————————————————————————————
  • My CSDN Blog

Java 语言简要介绍(JDK、JRE、JVM、JIT)

@[TOC](Java 语言简要介绍)
# 1. Java 语言的简要历史
Java之父:James Gosling 詹姆斯·高斯林

Java的前身是Oak语言;Oak去除了留在C++的一些不太实用及影响安全的成分,并结合了嵌入式系统的实时性要求

简要发展:
* 1995 年 Oak改名为Java,Java语言第一次面世
* 1996 年1 月 JDK 1.0发布,JVM为Sun Classic VM
(Sun Classic VM已经被淘汰,是第一款商用虚拟机,但只能使用纯解释器(没有JIT—Just in time编译器)的方法来执行Java代码)
* 1997 年 2 月 JDK 1.1发布
内部类、反射、jdbc、javabean、rmi
* 2014 年 Java 8发布
Lambda表达式 函数式接口 方法引用 默认方法 Stream;
* 2017 年 Java 9发布 新增了模块化
* 2018 年 Java 11发布,并且设计为长期支持的版本

目前市场上所用的多位Java 8和Java 11版本,本博及以后讨论的内容也主要集中在这两个版本。

# 2. Java 语言的特点
**1. Java 能够做什么**
* Web开发
* Android开发
* 游戏设计
* 软件开发

**2. Java 语言的优势**
**1. 简单**

Java最初是为对家用电器进行集成控制而设计的一种语言,因此它必须简单明了。Java语言的简单性主要体现在以下三个方面:

1) Java的风格类似于C++,因而C++程序员是非常熟悉的。从某种意义上讲,Java语言是C及C++语言的一个变种,因此,C++程序员可以很快就掌握Java编程技术。

2) Java摒弃了C++中容易引发程序错误的地方,如指针和内存管理。

3) Java提供了丰富的类库。

**2. 面向对象**

面向对象可以说是Java最重要的特性。

**3. 分布式**

Java包括一个支持HTTP和FTP等基于TCP/IP协议的子库。因此,Java应用程序可凭借URL打开并访问网络上的对象,其访问方式与访问本地文件系统几乎完全相同。为分布环境尤其是Internet提供的动态内容无疑是一项非常宏伟的任务,但Java的语法特性却使我们很容易地实现这项目标。

**4. 健壮**

Java致力于检查程序在编译和运行时的错误。类型检查帮助检查出许多开发早期出现的错误。Java自已操纵内存减少了内存出错的可能性。Java还实现了真数组,避免了覆盖数据的可能。这些功能特征大大缩短了开发Java应用程序的周期。Java提供 Null指针检测数组边界检测异常出口字节代码校验。

**5. 结构中立**

另外,为了建立Java作为网络的一个整体,Java将它的程序编译成一种结构中立的中间文件格式。只要有Java运行系统的机器都能执行这种中间代码。

**6. 安全**

Java的安全性可从两个方面得到保证。一方面,在Java语言里,像指针和释放内存等C++功能被删除,避免了非法内存操作。另一方面,当Java用来创建浏览器时,语言功能和浏览器本身提供的功能结合起来,使它更安全。Java语言在机器上执行前,要经过很多次的测试。它经过代码校验,检查代码段的格式,检测指针操作,对象操作是否过分以及试图改变一个对象的类型。

**7. 可移植的**

JAVA编译器产生的目标代码(J-Code) 是针对一种并不存在的CPU--JVM,而不是某一实际的CPU。JVM 能避免不同CPU之间的差别,使J-Code能运行于任何具有JVM的机器上。

作为一种虚拟的CPU,JVM对于源代码(Source Code) 来说是独立的。我们不仅可以用 Java语言来生成J-Code,也可以用其他语言来生成。源代码一经转换成J-Code以后,JVM就能够执行而不区分它是由哪种源代码生成的。这样做的结果就是CPU可移植性。 将源程序编译为J-Code的好处在于可运行于各种机器上,而缺点是它不如本机代码运行的速度快。

同体系结构无关的特性使得Java应用程序可以在配备了Java解释器和运行环境的任何计算机系统上运行,这成为Java应用软件便于移植的良好基础。但仅仅如此还不够。Java 中基本数据类型的范围是明确规定的,不依赖于具体实现,也将为程序的移植带来很大便利。

**8. 解释的**

Java解释器(运行系统)能直接运行目标代码指令。链接程序通常比编译程序所需资源少,所以程序员可以在创建源程序上花上更多的时间。

**9. 高性能**

如果解释器速度不慢,Java可以在运行时直接将目标代码翻译成机器指令。解释器一秒钟内可调用300,000个过程。翻译目标代码的速度与C/C++的性能没什么区别。

**10. 多线程**

多线程功能使得在一个程序里可同时执行多个小任务。Java实现的多线程技术,比C和C++更键壮。多线程带来的更大的好处是更好的交互性能和实时控制性能。当然实时控制性能还取决于系统本身(UNIX, Windows, Macintosh等),在开发难易程度和性能上都比单线程要好。例如,在Java里,可以用一个单线程来调一副图片的同时访问HTML里的其它信息而不必等待。

**11. 动态**

Java的动态特性是其面向对象设计方法的发展。它允许程序动态地装入运行过程中所需要的类,这是C++语言进行面向对象程序设计所无法实现的。Java从如下几方面采取措来解决这个问题。Java编译器不是将对实例变量和成员函数的引用编译为数值引用,而是将符号引用信息在字节码中保存下传递给解释器,再由解释器在完成动态连接类后,将符号引用信息转换为数值偏移量。这样,一个在存储器生成的对象不在编译过程中决定,而是延迟到运行时由解释器确定的。这样,对类中的变量和方法进行更新时就不至于影响现存的代码。解释执行字节码时,这种符号信息的查找和转换过程仅在一个新的名字出现时才进行一次,随后代码便可以全速执行。在运行时确定引用的好处是可以使用已被更新的类,而不必担心会影响原有的代码。如果程序连接了网络中另一系统中的某一类,该类的所有者也可以自由地对该类进行更新,而不会使任何引用该类的程序崩溃。Java还简化了使用一个升级的或全新的协议的方法。如果系统运行Java程序时遇到了不知怎样处理的程序,Java能自动下载所需要的功能程序。

**12. Unicode**

Java使用 Unicode作为它的标准字符,这项特性使得Java的程序能在不同语言的平台上都能撰写和执行。简单的说,你可以把程序中的变量、类别名称使用中文来表示,当你的程序移植到其它语言平台时,还是可以正常的执行。Java也是目前所有计算机语言当中,唯一被直接设计成使用Unicode的语言。

# 3. What is JDK、JRE & JVM?
- JDK (Java开发工具—Java Development Kit)包含了Java 语言、JRE(Java运行环境—Java Runtime Environment)和Java 工具库与API工具库这三部分,是Java程序开发的最小环境。

- JRE目录里面有两个文件夹bin和lib,可以简单地认为bin里的内容就是JVM,lib中则是JVM工作所需要的类库。

- JVM(Java虚拟机—Java Virtual Machine)包含在JRE中,是运行Java程序的核心虚拟机,有各种指令集和各种运行时数据区域。
JVM与Java语言没有必然的联系,它只与特定的二进制文件—Class文件有关;任何语言只要代码能被编译成Class文件,就可以被JVM机识别并执行。
只有JVM还不能让class文件执行,因为在解释class文件的时候JVM需要调用解释所需要的类库lib;

![JDK示意图](https://img-blog.csdnimg.cn/20200710233232649.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI0ODM1NA==,size_16,color_FFFFFF,t_70#pic_center)
# 4. Java 跨平台原理
Java单独开发了一套(各个平台)Java虚拟机的程序,编写的Java程序不需要运行在系统当中,而是运行在系统安装的Java虚拟机当中,各个平台上的Java虚拟机都能识别编写的Java程序。
1、Java的跨平台,是指Java在运行时是凌驾于操作系统(OS)之上,直接在JVM中运行的,与OS并没有直接关系,只要提供并且安装了相对应的虚拟机就可以跨该平台。

2、Java跨平台主要是由Java的编译方式决定的:Java是通过先编译再由JVM执行,编译后的.class文件是底层的实现,而不是针对OS的;故无论OS如何,并不影响Java代码的执行。

[下载安装:https://www.oracle.com/technetwork/java/javase/archive-139210.html)](https://www.oracle.com/technetwork/java/javase/archive-139210.html)

# 5. Java 程序的运行流程

Oracle JDK和OpenJDK中自带的虚拟机为HotSpot VM,也是最主流的和使用范围最广的Java虚拟机。(此处介绍的即是这种虚拟机)

JVM是如何把原文件转换为机器可识别语言的?

首先,编写的源程序(扩展名为.java),虚拟机通过JDK中的Java编译器,将源程序转化为Java二进制的字节码文件(扩展名为.class)。javac.exe可以简单看成是Java编译器。

转化为字节码后,通过Java解释器,可以将字节码在对应的操作系统下解释执行,就实现了机器可识别的语言了。java.exe可以简单看成是Java解释器。

由源文件(.java)--->字节码文件(.class)(二进制文件)-----> 解释---->Unix, Win, Linux等操作系统。

值得注意的是,即使源文件所在的目录位置并不正确,也能正常编译,但JVM无法找到与目录匹配的类,最终的程序不能执行。


**简要流程:**
**Step 1. Java前端编译器编译:把*.java文件转变成*.class文件**
```mermaid
flowchat
st=>start: Student.java
e=>end: Student.class
op=>operation: Java编译器
st->op->e
```
**Step 2. 传递.class文件**
**Step 3. 加载到JVM运行**
```mermaid
flowchat
st=>start: Student.class
op=>operation: JVM
st->op
```
**完整流程:**
![Java程序运行示意图](https://img-blog.csdnimg.cn/20200713182238428.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI0ODM1NA==,size_16,color_FFFFFF,t_70)

# 6. 类的加载机制
加载类主要由 JVM中的解释器负责执行,类的加载分为3个步骤:

1、装载(Load)

2、链接(Link)

3、初始化(Initialize)

## 6.1 装载
装载过程将查找并加载类的二进制数据(查找和导入Class文件)

也是类加载过程的第一个阶段,在加载二进制数据阶段,JVM需要完成以下三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。

2、按照JVM所需的格式,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口,以便该类对象访问方法区中的这些数据。

相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

## 6.2 链接
链接阶段主要分三个步骤:分别为:验证、准备和解析

### 6.2.1 验证:确保被加载的类的正确性

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:

文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。

元数据验证:对字节码描述的信息进行语义分析(并非javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。

字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

符号引用验证:确保解析动作能正确执行。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。


### 6.2.2 准备:为类的静态变量分配内存,并将其初始化为默认值

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。

2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、null、false等),而不是被在Java代码中被显式地赋予的值。

假设一个类变量的定义为:public static int value = 1; 那么变量value在准备阶段过后的初始值为0,而不是1,因为这时候尚未开始执行任何Java方法,而把value赋值为1的putstatic指令是在程序编译后,存放于类构造方法之中的,所以把value赋值为1的动作将在初始化阶段才会执行。

### 6.2.3 解析:把类中的符号引用转换为直接引用

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。


## 6.3 初始化:对类的静态变量,静态代码块执行初始化操作

初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在 Java中对类变量进行初始值设定有两种方式:

①声明类变量是指定初始值。

②使用静态代码块为类变量指定初始值。


# 7. JIT 简介
**解释器-Interpreter** 与 **JIT编译器-JIT Compiler**
后端运行期编译器 JIT 编译器是“动态编译器”的一种,指的是“在运行时进行编译”,把字节码转成机器码;与之相对的是静态提前编译器(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation),把*.java编译成本地机器码。

JIT 并不是 JVM的必须部分,JVM规范并没有规定JIT必须存在,或者限定 JIT的实现。但 JIT性能的好坏、代码优化程度的高低是衡量一款JVM是否优秀的关键指标之一,也是虚拟机中核心且最能体现 JVM技术水平的部分。

HotSpot虚拟机中内置了两个即时编译器:Client Complier和Server Complier,简称为C1、C2编译器,分别用在客户端和服务端。目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器直接配合的方式工作。程序使用哪个编译器,取决于虚拟机运行的模式。HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用“-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式。

用Client Complier获取更高的编译速度,用Server Complier 来获取更好的编译质量。为什么提供多个即时编译器与为什么提供多个垃圾收集器类似,都是为了适应不同的应用场景。

![解释器-Interpreter 与编译器-Compiler的交互](https://img-blog.csdnimg.cn/20200713192541269.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI0ODM1NA==,size_16,color_FFFFFF,t_70)
**解释器和编译器交互的作用**

JVM有两种执行方式: 解释执行和编译执行

对于解释执行,不经过 JIT编译,直接由解释器解释执行所有字节码,执行效率不高。 而编译执行不加筛选的将全部代码进行编译机器码不论其执行频率是否有编译价值。(因为JIT的编译是首次运行或启动的时候进行的!)

在运行时,JIT会把翻译过来的机器码保存起来,以备下次使用。而如果 JIT对每条字节码都进行编译,则负担过重,所以,JIT只会对经常执行的字节码进行编译,如循环等高频度使用的方法。它会以整个方法为单位,一次性将整个方法的字节码编译为本地机器码并且保存起来,随后直接运行编译后的机器码。此外,如果编译后出现“罕见陷阱”,可以通过逆优化退回到解释执行。

这也是为什么 Java 本身是一种半编译半解释执行的语言。
![JVM混合执行模式](https://img-blog.csdnimg.cn/20200715190440162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI0ODM1NA==,size_16,color_FFFFFF,t_70)
**混合使用的好处:**

1> **提高运行效率。** 如果一段代码仅仅会被执行一次或很少次,那么编译就显得有些浪费时间。此时,将代码通过 Interpreter 解释执行相对于 JIT Compiler 编译这段代码再执行来说要快很多。

通过 Interpreter 解释执行的代码/块一般有:
1、只被调用一次的方法,eg: 类的构造器
2、无循环体的简单语句

但如果某段代码频繁调用某个方法或是执行某个循环,那么就需要编译。待编译字节码会交由 JIT compiler 处理,将运行频率很高的字节码直接编译并储存为本地机器码执行以提高性能。

2> **优化。** 当 JVM 执行某一方法或遍历循环的次数越多,JVM就会越熟悉代码结构,以便在编译代码的时候就做出相应的优化。

例如:equals() 这个方法存在于每一个 Java 对象中(因为是从 Object 继承而来)而且经常被覆写。当 Compiler 遇到 b = obj1.equals(obj2) 这样一句代码,则会查询 obj1 的类型从而得知到底运行哪一个 equals() 方法,但这个动态查询的过程是很耗时的。

在 JVM中,Java程序一开始是通过 Interpreter 进行解释执行的。当 JVM发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“热点代码(Hot Spot Code)”,然后 JVM会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化。

**参考内容:**
1. [浅谈对JIT编译器的理解 by Stubborn](https://www.cnblogs.com/insistence/p/5901457.html)
2. [JAVA高级篇(三、JVM编译机制、类加载机制) by 夕Orange](https://www.cnblogs.com/XiOrang/p/9355589.html)
3. [什么是JIT by soar-csdn](https://blog.csdn.net/xzh121121/article/details/103802794?ops_request_misc=&request_id=&biz_id=102&utm_term=JIT&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-103802794)
4. [JAVA语言主要特点有哪些? by Jay36](https://www.cnblogs.com/jay36/p/7762535.html)
5. “Core Java Volume I - Fundamentals” (11 Edition) by Cay S. Horstmann
6. [JVM即时编译(JIT)by dongworyy](https://www.cnblogs.com/doyi111/p/12054863.html)
***If you have any question, please let me know, your words are always welcome.***
新人入坑,如有错误/不妥之处,欢迎指出,共同学习。

posted @ 2020-07-03 15:43  Johnson1z  阅读(499)  评论(0编辑  收藏  举报