Java
JDK、 JRE、JVM之间的区别
一、 引言
在Java环境配置和项目启动中,这三者的配置是项目启动的基础保证,但这三者的作用和区别呢,本文将对JVM、JRE、JDK的作用与区别进行讲解。
注:如果只是运行Java程序,只需要JRE即可,JRE通常非常小,其中包含了JVM,如果要开发Java程序,就需要安装JDK。
二、方法
JVM,JRE,JDK定义
JVM(Java Virtual Machine)就是一个虚拟的用于执行bytecode字节码的”虚拟计算机”。一般与OS操作系统打交道。
JRE(Java Runtime Environment),包含Java虚拟机、库函数、运行Java应用程序所必须的文件。
JDK(Java Development Kit),包含JRE,以及增加编译器和调试器等用于程序开发的文件。三者的区别与联系
联系
JVM不能单独搞定class的执行,解释class的时候JVM需要调用解释所需要的类库lib。在JDK下面的JRE目录里面有两个文件夹bin和lib,在这里可以认为bin就是JVM,lib中则是JVM工作所需要的类库,而JVM和lib和起来就称为JRE。JVM+lib=JRE。总体来说,就是我们利用JDK(调用Java Api)开发了属于我们自己的Java程序后,通过JDK中的编译程序(javac)将我们的文本java文件编译成Java字节码,在JRE上运行这些java字节码,JVM解析这些字节码,映射到CPU指令集或OS的系统调用。区别
JDK和JRE区别:在bin文件夹目录下会发现,JDK有javac.exe而JRE里面没有,javac指令是用来将java文件编译成class文件的,这是开发者需要的,而用户(运行程序的人)是不需要的。JDK还有jar.exe,javadoc.exe等等用于开发的可执行指令文件。这也证实了一个是开发环境,一个是运行环境。
JRE和JVM区别:JVM并不代表就可以执行class了,JVM执行.class还需要JRE下的lib类库的支持,尤其是rt.jar。
通过对JVM,JRE,JDK的描述,我们就能看出三者之间的关系:
JDK: javac,jar,debugging,tools,javap
JRE:java,javaw,libraries,rt_jar
JVM:just in Time Compiler(JIT)
三、结语
搞懂三者的关系,能够让我们对java底层运行原理有一个良好的认知基础,以及加强我们对环境配置的了解。
弄懂 JRE、JDK、JVM 之间的区别与联系
其实很多 Java 程序员在写了很多代码后,你问他 jre 和 jdk 之间有什么关系,jvm 又是什么东西,很多人不知所云。本篇不会讲述 jvm 底层是如何与不同的系统进行交互的,而主要理清楚三者之间的区别,搞清楚我们写的 xxx.java 文件是被谁编译,又被谁执行,为什么能够跨平台运行。
首先,我们分别对这三者进行阐述。
JVM :英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,jvm 是 Java 能够跨平台的核心,具体的下文会详细说明。
JRE :英文名称(Java Runtime Environment),我们叫它:Java 运行时环境。它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。
JDK :英文名称(Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
显然,这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM。
总结:JDK = JRE + Java开发工具
JRE = JVM + Java类库
为什么我们的电脑在装完 jdk 后会有两个版本的 jre?
留心的同学可能会发现,我们的 jdk 安装成功后,在 C:\Program Files\Java 目录会是这样的(这里,我是装的 jdk1.8 的版本)
而 jdk 的子目录下也存在一个 jre。
这两个不同版本的 jre 相互之间有什么联系吗?
答案是:没有联系。甚至准确的来说,它俩是一样的,无论是用哪一个都是可以的。只是很多人习惯将会单独安装另一个 jre,虽然单独安装的 jre 也并没有被使用,原因可能就是刚开始大家都不清楚 jdk 和 jre 之间的关系,所以就默认的都安装上了。
在 jdk 的 bin 目录下,基本上都是一些可执行文件,并且它们还不大。其实这些可执行文件只是外层的一层封装而已,这样的目的是避免输入的命令过长。例如 javac.exe 内部调用的其实是 JDK 中 lib 目录中的 tools.jar 中 com.sun.tools.javac.Main 类,也就是说这些工具只是入口而已。而实际上它们本身又都是由 Java 编写的,所以在 jdk 目录下的 jre 既提供了这些工具的运行时环境,也提供了我们编写完成的 Java 程序的运行时环境。
所以,很明显,jdk 是我们的开发工具包,它集成了 jre ,因此我们在安装 jdk 的时候可以选择不再安装 jre 而直接使用 jdk 中的 jre 运行我们的 Java 程序。(但是大部分人都默认将两个都装上了)。但是如果你的电脑不是用来开发 Java 程序的,而仅仅是用来部署和运行 Java 程序的,那么完全可以不用安装 jdk,只需要安装 jre 即可。
Java 为什么能跨平台,实现一次编写,多处运行?
Java 能够跨平台运行的核心在于 JVM 。不是 Java 能够跨平台,而是它的 jvm 能够跨平台。我们知道,不同的操作系统向上的 API 肯定是不同的,那么如果我们想要写一段代码调用系统的声音设备,就需要针对不同系统的 API 写出不同的代码来完成动作。
而 Java 引入了字节码的概念,jvm 只能认识字节码,并将它们解释到系统的 API 调用。针对不同的系统有不同的 jvm 实现,有 Linux 版本的 jvm 实现,也有 Windows 版本的 jvm 实现,但是同一段代码在编译后的字节码是一样的。引用上面的例子,在 Java API 层面,我们调用系统声音设备的代码是唯一的,和系统无关,编译生成的字节码也是唯一的。但是同一段字节码,在不同的 jvm 实现上会映射到不同系统的 API 调用,从而实现代码的不加修改即可跨平台运行。
类的加载过程
注意:这些阶段的顺序虽然是确定的,但是这些阶段通常都是互相交叉混合进行的,会在一个阶段中调用,激活另外一个阶段执行
加载
加载阶段顾名思义,也就是Class文件所代表的的 类/接口 被加载到虚拟机中。
那么什么时候(类加载的时机), 谁来加载(哪个类加载器),将哪个Class文件(Class的名称是什么)到虚拟机中呢?
1.什么时候加载?
《JAVA虚拟机规范》中没有进行强制约束,由不同虚拟机来决定加载的时机,也就是不同虚拟机进行加载的时机是不同的。
2.谁来加载?
通过类加载器进行加载,一个类必须和类加载器一起确定唯一性。由于本文重点阐述的是类加载的阶段,故下篇文章对类加载器进行阐述。
3.哪个Class文件?
需要程序指定 类/接口的全限定名(包名+类/接口名)。
因此可以得出加载阶段做的事情:
1、通过一个类的全限定名来获取定义此类的二进制字节流
2、将这个字节流所代表的的静态存储结构转换为方法区的运行时数据结构
3、在堆内存中生成一个代表这个类的java.ang.Class对象,作为方法区这个类的访问入口
获取二进制流
这条规则虚拟机规范中并没有指明说从哪里获取,如何获取,只是说通过全限定名获取二进制字节流就行。
因此我们可以从压缩包(JAR),网络(Web Applet),加密文件(加载时动态解密),运行时生成(动态代理)…这些路径中通过类的全限定名去获取二进制字节流进行加载。
将字节流转换为运行时数据结构
要想把字节流转换成方法区的运行时数据结构,自然一定是要经过一些验证,也就是验证阶段的文件格式验证;
文件格式验证阶段没有出现问题的话,接下来就会按照虚拟机中方法区的数据存储格式将数据存储到方法区之中(方法区的数据结构并没有明确规定,因此不同虚拟机实现的结构也是不一样的)。
正如开篇所说,这些阶段是互相交叉混合进行的,但是他们的顺序是确定的。这个很好理解,没有加载阶段获得的二进制字节流,那么验证阶段是不知道验证什么的。
堆中生成Class对象
也就是下图中的步骤:
特殊
上面所说的都是非数组类型的加载阶段,开发者可以根据自定义类加载器来获取二进制字节流,来做一些骚操作。
而对于数组类来说,数组不是通过类加载器进行创建的,而是虚拟机在内存中动态构造出来的。但是数组的元素类型却需要通过类加载器来进行加载。
数组元素类型就是去掉所有维度的类型,比如一个int类型的一维数组和一个int类型的二维数组,他们两个的元素类型都是int。也可以简单理解为和维度无关。
数组类型的加载:
连接
验证
这一步骤是确保Class文件的字节流中包含的信息要符合虚拟机规范中的要求,保证这些信息被当做代码运行后不会危害虚拟机自身的安全。
1.为什么需要验证阶段?
上面的加载阶段中说过,二进制字节流的来源可以有很多,当然也可以自己手写0和1,如果不对这些字节流进行验证的话,可能会因为加载了错误或者恶意的代码使整个系统崩溃。
所以字节码验证是必须的阶段,这个阶段决定了虚拟机的健壮性,使得虚拟机不那么轻易被攻击,因此在代码量和耗费的性能上来说,验证阶段的工作量在类加载过程中是占比非常大的。
2.验证哪些内容?
验证过程的描述在《JAVA虚拟机规范(Java SE 7版)》中长达130页,有兴趣的读者可以自己搜索,本文仅对整个阶段中的重要验证部分讲解。
2.1,文件格式验证
该步骤在上面的加载过程中已经提到,当加载阶段将字节流的数据存储到方法区中的数据结构中时需要对Class的文件格式进行验证。
当文件格式验证通过后字节流的数据信息就已经被存储到方法区中的数据结构中了,因此之后的验证阶段都不是直接对二进制流进行操作了,而是对方法区中的数据结构进行验证。
目的:
该步骤是验证字节流是否符合Class文件格式的规范,保证字节流的数据能够正确解析并存储到方法区的数据结构中,而且当前的虚拟机版本能够对其进行处理
验证点,eg:
1.magic是否正确
2.主版本号和次版本号是否在当前虚拟机版本能够处理的范围
3.常量池中的常量是否有不被支持的常量类型(tag标志)
4.是否有指向常量池中不存在的索引
2.2,元数据验证
对字节码描述的信息进行语义分析,保证其描述的信息符合《JAVA语言规范》的要求
简单理解就是对类的元数据信息进行验证,比如对父类的信息检查,类字段方法定义,数据类型校验
验证点,eg:
1.这个类是否有父类
2.这个类的父类是不是继承了不允许被继承的类(final修饰的类无法被继承)
3.如果这个类不是抽象类的话,是否已经实现了父类或者接口中的所有方法
4.类中的字段,方法是否与父类产生矛盾(修改了父类的final字段,…)
2.3字节码验证
上一步对元数据进行验证后,接下来就是对方法体进行验证了。
其实这步应该叫code属性的校验(Code为类中的方法体属性)比较准确。
整个验证过程最复杂的一步,通过数据流和控制流来分析程序的语义是合法,符合逻辑的。保证方法运行时不会做出错误或者危害虚拟机的行为。
验证点,eg:
1.保证操作数栈的数据类型的指令代码序列中操作的数据类型一致,不会出现操作数栈是int,指令代码取出的时候按照long来取
2.跳转指令不会跳转到方法体以外的字节码指令上
3.保证类型转换是正确的
通过了Code属性的验证也不一定代表就是方法体中的代码就是安全的,不可能用程序来判定一段程序是否存在Bug(感兴趣的读者可以搜索“停机问题”),这是离散数学中的一个问题。
参考
https://www.cnblogs.com/iamfatotaku/p/14517223.html
https://blog.csdn.net/qq_45272690/article/details/122443424
网络
TCP的三次握手和四次挥手
TCP协议是7层网络协议中的传输层协议,负责数据的可靠传输。在建立TCP连接时,需要通过三次握手来建立,过程是:
- 客户端向服务端发送一个SYN
- 服务端接收到SYN后,给客户端发送一个SYN_ACK
- 客户端接收到SYN_ACK后,再给服务端发送一个ACK
在断开TCP连接时,需要通过四次挥手来断开,过程是:
- 客户端向服务端发送FIN
- 服务端接收FIN后,向客户端发送ACK,表示我接收到了断开连接的请求,客户端你可以不发数据了,不过服务端这边可能还有数据正在处理
- 服务端处理完所有数据后,向客户端发送FIN,表示服务端现在可以断开连接4·客户端收到服务端的FIN,向服务端发送ACK,表示客户端也会断开连接了
SYN是同步序列编号(Synchronize Sequence Number)的缩写,ACK是确认(Acknowledgement)的缩写。
客户端向服务端发送一个SYN,表示请求建立连接,并告诉服务端客户端的初始序列号。
服务端接收到SYN后,给客户端发送一个SYN_ACK,表示确认客户端的请求,并告诉客户端服务端的初始序列号。
客户端接收到SYN_ACK后,再给服务端发送一个ACK,表示确认服务端的响应,并告诉服务端客户端的下一个序列号。
因此,ACK中既包含了确认服务端的响应,也包含了客户端的序列号。
SYN是Synchronize的缩写,是TCP协议中用于建立连接的同步标志。ACK是Acknowledgement的缩写,表示确认收到了对方发送的数据。在TCP三次握手过程中,SYN和ACK分别表示以下含义:
客户端发送SYN:表示客户端请求建立连接
服务端发送SYN_ACK:表示服务端接收到请求并同意建立连接
客户端发送ACK:表示客户端收到了服务端的同意,并确认连接已建立
因此,在TCP三次握手过程中,SYN和ACK都是用于建立连接和确认收到数据的标志。