JVM面试(一)什么是虚拟机?什么是class文件?
什么是java虚拟机?
如果通俗点来讲,我们在电脑上一行行敲出来的代码,电脑本身是不认识的,最终是要转成电脑可以运行的101001这种字节。
但是这些我们又不可能手动来转换,所以呢,就需要一个工具,来将我们敲出来的代码,转换成电脑可以认识的命令来进行执行。
为了使用方便,就把这个可实时运行的程序,封装取来,拿到任何地方都可以使用。而且因为最支持的语言就是java,所以取一个名字叫做java虚拟机。
那在真正使用的时候,我们到底有接触吗?
有, 就是每个java开发人员都要在电脑上安装的运行环境,JRE,这个就包含了java的虚拟机,还有一些基础的类库(也就是基础的jar包)。
那有的同学就说了,我们没有安装JRE,只安装了JDK啊。
对,JDK中,包含了java开发时候一些常用的工具包(jar/lib),同时还包含了JRE,以及编译器,
编译器就是我们配置jdk环境之后,都会输入的一个命令“javac”,作用就是将我们的开发文件".java" ,编译为".class"
".class"文件就是虚拟机运行时候可识别的文件,也就是说我们开发是java文件,虚拟机运行之前将其先编译为class文件,用这些class文件在虚拟机中运行,与计算机本身产生交互。
什么是跨平台?
为什么java语言可以实现跨平台? 就是因为编译之后,可以带着虚拟机,在任何地方运行。
并且,其他语言中,只要编译为class文件,都可以在虚拟机中运行,这样就能与java文件产生交互,这就叫跨平台,如下图:
也就是说,其实java虚拟机,并不是与java语言强相关。其他语言只要编译为class文件,就都可以在java虚拟机中运行,这个就叫做跨平台。
什么是class文件?
就是上面我们说过的,java虚拟机可识别的文件,由我们开发出来的java文件编译出来。
结构如下
- ⽆符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个 字节的⽆符号数,⽆符号数可以⽤来描述数字、索引引⽤、数量值或者按照UTF-8编码构成字符串值。
- 表是由多个⽆符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。表⽤于描述有层次关系的复合结构的数据,整个Class⽂件本质上也可以视作是⼀张表,这张表由表6-1所示的数据项按严格顺序排列构成。
魔数和主次版本号
魔数固定为CAFEBABE
次版本号在jdk12之前均没被使⽤,默认为0。主版本号如jdk8中对应为⼗进制的版本号为52;
常量池
(constant_pool_count)常量池计数器,从1开始计数,表示class中常量池的⼤⼩。
常量池中主要存放两⼤类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语⾔层⾯的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译 原理方面的概念,主要包括下面几类常量:
- 被模块导出或者开放的包(Package)
- 类和接⼝的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- ⽅法的名称和描述符
- ⽅法句柄和⽅法类型(Method Handle、Method Type、Invoke Dynamic)
- 动态调⽤点和动态常量(Dynamically-Computed Call Site、Dynamically Computed Constant)
Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这⼀步骤,而是在虚拟机加载Class 文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段最终在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
PS:上面这段话有点拗口,简单总结来说,就是虚拟机的运行并不直接与内存产生联系,而是在的请求创建的线程中,再与内存产生联系,线程拿到数据拷贝进行处理。
访问标志
在常量池结束之后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接接口;是否定义为public类型;是否定义为abstract 类型;如果是类的话,是否被声明为final;等等
类索引、父类索引与接口索引集合
- 类索引(this_class)和父类索引(super_class)都是⼀个u2类型的数据,而接口索引集合(interfaces)是⼀组u2类型的数据的集合,Class文件中由这三项数据来确定该类型的继承关系。
- 类索引用于确定这个类的全限定名,父类索引⽤于确定这个类的父类的全限定名。
- 由于Java语⾔不允许多重继承,所以父类索引只有⼀个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的⽗类索引都不为0。
- 接⼝索引集合就⽤来描述这个类实现了哪些接口,这些被实现的接⼝将按implements关键字(如果这个Class⽂件表示的是⼀个接口,则应当是extends关键字)后的接口顺序从左到右排列在接口索引集合中。
字段表集合
字段表(field_info)用于描述接⼝或者类中声明的变量。Java语⾔中的“字段”(Field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
方法表集合
方法表⽤于描述接⼝或类中声明的方法。⽅法表的结构如同字段表⼀样,依 次包括访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。
对于方法里的Java代码,经过Javac编译器编译成字节码指令之后,存放在方法属性表集合中⼀个名为“Code”的属性里面
属性表集合
属性表(attribute_info)。Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。