一.有关JVM简介
1.定义
JVM即java虚拟机,它是一款用于运行java字节码文件的程序。
JVM的三大运行功能:解释运行,内存管理,即时编译
(1)解释运行
前言:编程语言可以划分为编译型语言(如C++)和解释型语言(如Python),而java则是一种混合型语言,兼备两者特性,也正因如此java语言的实现了“一次编译,到处运行”的特点,但是也奠定了java语言在性能方面很难超过C++的问题,针对此问题,Oracle公司以及之前的Sun公司也进行了大量的优化,在性能方面得到了很大的提升。
java源文件在经由软件工程师编写后由java编译器进行编译操作成class字节码文件,然后再由java虚拟机进行运行,而其中class字节码文件存储的其实是字节码指令,这种字节码指令计算机本身是看不懂的他需要由java虚拟机(JVM)进行解释成计算机可以看懂的机器码并运行,由于在不同操作系统的机器码有差异,所以很多语言(如C++)等可以会因此有兼容性问题,但是java不会,因为它是实时解释的。
注:写作者本人也就是我在翻阅资料是发现很多次资料对此的描述都是有问题的,一些人认为JVM在编译时也进行了操作,但这其实是错误的,虚拟机只进行class文件的运行以及解释,并不会编译,编译的过程是由java编译器(如idea等)完成的。
(2)内存管理
注:在本篇文章中我只会进行概括性的说明,具体说明未来会发文章来讲述
JVM会自动为对象,方法等进行分配内存,且自动回收不在使用的对象,即垃圾回收,这在部分语言其实是很苦恼的部分,但在java中JVM帮助我们完成了这点。
(3)即时编译
在发现java性能低的问题时,开发者对JVM进行了优化,在第一次对字节码文件进行解释时,会将机器码保存至内存中,从而在第二次进行运行时便可以直接调用,免去了解释的步骤。
注:很多人认为虚拟机就是虚拟机,但是虚拟机其实是有为许多的,如HotSpot,GraalVM等,我们日常用的都是Oracle的HotSpot的闭源的版本,但是不同的虚拟机特点不同,使用场景也是不同的,我在此不进行赘述。
2JDK和JRE
JDK:java程序设计语言,java虚拟机,java API -------主持java开发的最小环境。
JRE:Java API中的Javase的API,Java虚拟机 -------支持java程序运行的标准环境。
JDK包含JRE。
3.虚拟机的组成
(1).类加载器(ClassLoader):用于加载clas字节码文件中的内容到内存中去。(个人理解:搬运工)
(2).运行时数据存储:负责管理JVM使用到的内存,比如创建对象和销毁对象。(个人理解:仓库加管理员)
(3).执行引擎:将字节码文件的指令解释成机器码,同时进行优化(个人理解:经理,告诉工人干啥)
(4).本地接口:调用以及编译的方法,如虚拟机中的C/C++方法,至于为什么会有C/C++方法,因为很多虚拟机是由C/C++写的。
二.字节码文件
1.字节码文件的组成
注:我是通过jclasslib插件打开的class文件,该工具是在github上开源的
字节码文件的基本组成:基础信息,常量池,字段,方法,属性
(1)部分基本信息
a.魔数(Magic):是指在文件格式中用来识别文件类型或格式的固定值或标识。由于在计算机中文件的后缀名可以随意更改,所以计算机无法保障视频资源是视频资源,class是class即无法保证资源的正确性,这是魔数就起作用了,文件在执行前会对比魔数来判断资源的正确性,如java的魔数就是前4位cafebaby(以16进制的形式打开可以看到,我是使用的WinHex软件打开的),不同的文件类型对应的魔数是不同的。
b.版本号:版本号分为主版本号和副版本号
其中副版本号只是进行了一些微调
主版本号则是每使用一个更新版本,版本号会+1,jdk1.0为44。jdk1.1为45,以此类推,由此可以计算出jdk版本,当报出版本号不兼容异常时,如类文件具由错误版本时,请将第三方依赖降低或者更会依赖
(2)常量池
JVM(Java Virtual Machine)常量池是为每个类和接口创建的一种存储常量的内存区域。常量池包含了类文件中的各种字面量和符号引用,如字符串、数字常量、类和接口的全限定名、字段和方法的名称和描述符等。在运行Java程序时,JVM会使用常量池来存储和查找这些常量。
注:针对于次内容,本人只是写了概念,具体内容以后会单独发一篇文章
(3)字段
当前类或接口声明的字段信息
(4)方法
字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容放在方法的Code属性中。(利用工具jclasslib级可以看到)
1.常见的字节码指令
在计算机操作数据时,计算机会将数据放在操作数栈中进行临时存放,最终将保存在变量表之中
图中第一行的iconst_0为在操作数栈中加载一个值,该值为0
istore_1是把该值放在变量表中的第一位(即i的位置)
iload_1是将变量表中的第一位的值取出到
iinc 1 by 1的意思是将变量表中第一位的值进行加一操作,第一个一是本地的位置,第二个是值
注:iinc 1 by 1这条指令是直接操作变量表的,而没有经历操作数栈的
其他的一些指令可以去oracle的官网进行查看
注:变量表:
指令对应的程序源码:
该程序是一道很经典的面试题,也很简单,输出的结果是0,只需分析指令就会发现在4行+1操作后,在操作数栈上的0在第5行的指令又会被覆盖为0。
2.对于程序效率的细节
如果细心查看图片,不难看出i++,j+=1的效率更加快捷,因为他们只执行了一条指令,而k=k+1的指令则足足运行了4条指令,这也颠覆了所谓i++和i=i+1相同的说法。
文章写的内容其实是很浅显的,很多更加底层与细节未来也会发文章进行说明的。