备战面试之Java虚拟机
1、谈一谈JDK、JRE、JVM三者之间的区别与联系
首先谈一谈Java程序的运行流程,由于Java程序号称是一门跨平台的语言,能够一次编译,到处运行,要实现这一点,需要首先将Java源代码编译成.class文件,然后借助Java虚拟机执行.class文件,而Java虚拟机在不同的平台上有不同的实现,相当于是利用Java虚拟机抹平了平台的差异性。
因此说白了,运行Java程序就是利用Java虚拟机运行.class文件,这里的Java虚拟机就是JVM,而JVM运行.class文件的时候,还需要加载一些基础类库也就是JavaSE API,JVM加这些基础类库就称为JRE,这是Java程序运行的最小环境,如果需要使用Java开发程序,还需要一些工具比如编译工具Javac,打包工具jar,程序运行监控工具jconsole等,JRE加上这些工具就组成了开发Java程序的最小环境,称为JDK。
2、JVM字节码文件结构
字节码使JVM执行程序的输入,字节码文件通过javap工具编译得到,在《Java虚拟机规范中》规定,字节码文件的开头为魔数和版本号,剩下的包括字节码常量池、类权限、父类在常量池中的引用、接口信息、字段信息、方法信息等。用这些信息表示描述一个Java类。
3、JVM的类加载机制
JVM要执行字节码首先需要进行类加载,这是程序执行的第一步,类加载要经过加载和初始化,在加载和初始化之间还要进行类的链接。
- 加载:其中类加载就是将字节码文件加载到内存中,类加载需要通过类加载器完成,在HotSpot中类加载过程分为加载和解析两步,加载就是读取磁盘上的class文件,解析就是根据Java虚拟机规范规定的字节码格式去解读class各个部分并且将其存入到方法区也就是HotSpot虚拟机的Metaspace,类加载的结果就是得到一个完整的用于描述类型信息的对象,在HotSpot中用InstanceKlass描述。
- 链接:类加载结束后在进行初始化之前需要进行类的链接,链接的目的是使类里面的方法具有可执行能力,虽然类已经被加载了,但是并不知道字节码的结构是否正确,而且其中的方法等都只是符号引用,无法通过符号引用从内存中找到具体的方法等,而且没有给方法设置执行引擎的入口,因此这时候类里面的方法是不具备可执行能力的,因此链接过程包括要验证字节码正确性,将符号引用替换为可寻址的直接引用,给方法设置执行引擎入口,并且给静态变量分配内存并设置初始值等步骤。
- 初始化:类链接结束后就是类的初始化,初始化就是执行字节码中的方法,这是由类的构造函数和静态语句块中的语句合并而成的,可以看到初始化阶段已经开始执行方法了,因此类链接的主要目的就是使类里面的方法具有可执行能力。
4、JVM中的类加载器
在HotSpot中,有三种类加载器,分别是Bootstrap类加载器,Platform类加载器和Application类加载器,它们三个依次构成父子关系,其中一些核心的类比如Thread类,Object类只能由Bootstrap类加载器去加载,进行类加载的时候,需要首先向上委派父加载器去加载,只有当父加载器无法完成类加载时才由自己去加载,这就是双亲委派机制,这样做的目的主要是为了避免类被重复加载,而且保证了核心类只能由BootStrap类加载器加载。
5、谈一谈JVM的运行时数据区
《Java虚拟机规范中》中指出,Java虚拟机的内存应该包括以下几个部分,分别是线程共享的方法区、堆区,以及线程隔离的虚拟机栈、本地方法栈、程序计数器。初次之外JVM的运行时数据区还包括方法执行引擎,本地方法库和本地接口。
- 方法区:一般来说方法区存放的是经过类加载器加载后的类型信息,常量池表,代码缓存Code Cache等数据。
- 堆区:一般来说,堆区又称Java堆,其中存放的是Java对象实例。
- 栈区:栈区跟方法调用有关,一般来说栈区分为虚拟机栈、本地方法栈和程序计数器,在JVM中,被调用的方法一般分为两种,一种是用Java写的Java方法,一种是由C/C++或者写的本地方法,而虚拟机栈和本地方法栈就对应这两种方法的调用,在HotSpot中,这两个栈被合为一个。
- 程序技术计数器:还有一个跟方法调用相关的内存区域是程序计数器,程序计数器可以被当作是当前线程所执行的字节码的行号指示器,字节码解释器就是通过这个计数器的值来选取下一条需要执行的字节码指令。
- 方法执行引擎:方法执行引擎用于执行方法,在JVM中,方法的执行分为解释执行和编译执行两种,所谓解释执行就是将字节码指令逐条翻译成机器码后执行,所谓编译执行就是将整段字节码全部翻译成机器码之后执行,两者各有优劣,解释执行的优点是执行比较实时,程序启动快,而编译执行只要一次编译过程,而且一般会对编译结果做优化,因此实际执行速度要比解释执行快很多,在HotSpot中,大部分时间都是通过模板解释器解释执行的,当发现所谓的热点代码的时候就使用编译器编译成机器码存放在Code Cache中,以后就可以反复使用这些热点代码,提升了执行速度。当使用模板解释器解释执行时,使用到的运行时结构时栈结构,也就是每次方法调用以栈帧的形式存与栈中,栈顶的元素即为正在执行的元素,在Java虚拟机中,栈帧包括以下几个部分,分别是局部变量表,用于存放方法参数和方法内的局部变量,动态连接的目的时将符号引用转化为可以定位内存位置的的直接引用;操作数栈,也是一个栈结构,它是执行字节码并存放每一步执行结果的结构;方法返回地址,方法退出的时候,通过方法返回地址可以恢复到上层的主调方法。
- 本地方法库和本地接口:本地方法库和本地及接口是JVM的一个重要组成部分,其中本地接口又称为JNI,它是打通Java程序到JVM的一个桥梁,本地方法定义了一些接口,这些接口的实现往往牵涉到一些底层细节,实现部分由JVM完成,Java程序只需要掉用即可,比如在Java中使用反射机制获取类型信息,以及通过Thread类创建线程时,内部都是通过调用本地方法接口完成的。
6、JVM中的方法执行引擎
方法执行引擎用于执行方法,在JVM中,方法的执行分为解释执行和编译执行两种,所谓解释执行就是将字节码指令逐条翻译成机器码后执行,所谓编译执行就是将整段字节码全部翻译成机器码之后执行,两者各有优劣,解释执行的优点是执行比较实时,程序启动快,而编译执行只要一次编译过程,而且一般会对编译结果做优化,因此实际执行速度要比解释执行快很多,在HotSpot中,大部分时间都是通过模板解释器解释执行的,当发现所谓的热点代码的时候就使用编译器编译成机器码存放在Code Cache中,以后就可以反复使用这些热点代码,提升了执行速度。当使用模板解释器解释执行时,使用到的运行时结构时栈结构,也就是每次方法调用以栈帧的形式存与栈中,栈顶的元素即为正在执行的元素,在Java虚拟机中,栈帧包括以下几个部分,分别是局部变量表,用于存放方法参数和方法内的局部变量,动态连接的目的时将符号引用转化为可以定位内存位置的的直接引用;操作数栈,也是一个栈结构,它是执行字节码并存放每一步执行结果的结构;方法返回地址,方法退出的时候,通过方法返回地址可以恢复到上层的主调方法。
7、JVM中的线程模型
线程是调度CPU执行的最小单位,使用多线程模型需要解决多个线程之间的调度过程以及多线程引发的线程安全问题,在HotSpot中定义了许多线程,包括虚拟机线程、Java线程、GC线程等,每个线程对应一个内核线程,因此在HotSpot中,线程调度问题实际上就转化为了操作系统对内核线程的调度问题,避免了开发虚拟机的时候自己实现线程调度,针对线程安全,Java提供了诸如synchronized同步机制,final关键字设置变量为不可变、volatile关键字保证变量可见性来解决线程安全问题,初次之外还可以通过可重入锁已经非阻塞机制如CAS算法等来是实现线程安全。
使用多线程模型需要了解线程的声明周期,Java中的线程状态有新建状态、可运行状态、运行状态、阻塞状态、结束状态,其中阻塞状态又包括线程进入等待状态,睡眠状态或者同步阻塞状态。