1 简介
Java Virtual Machine(JVM,Java虚拟机)是一种软件,Java运行的操作系统。JVM由两个系统和两个组件组成。其中,两个系统分别为类加载(Class Loader)子系统和执行引擎(Execution Engine)子系统;两个组件分别为运行时数据区(Runtime Data Area)组件和本地接口(Native Interface)组件,JVM结构如图1所示。
2 JVM各部分功能
2.1 类加载子系统
根据给定的全限定名类名,如java.lang.Object装载class文件内容到运行时数据区的方法区中,开发者可以通过继承java.lang.ClassLoader类自定义ClassLoader。
2.2 执行引擎子系统
执行classes中的指令,任何JDK实现的核心都是执行引擎,不同JDK的性能取决于执行引擎。
2.3 本地接口组件
本地接口与本地库(Native Libraries)交互,是其他编程语言交互的接口,当调用本地方法是,即进入了全新的不受虚拟机限制的区域,因此很容易出现JVM无法控制的本地堆内存泄露。
- 本地方法:
Native Method是Java调用非Java代码的接口,Native Method有Java定义接口,实现由其他语言C/C++实现方法,因为,部分与硬件交互的任务用Java实现困难且性能不高,所以,使用其他高级语言C/C++实现,用Native Method调用。
2.4 运行时数据区组件
由五部分组成,分别为堆(Heap)、方法区(Method Area)、Java栈(Stack)、程序计数器(Program Counter,PC)和本地方法栈(Native Method Stack)。
- 堆
一个Java虚拟实例中只存在一个堆空间,线程共享,该区域中保存所有引用数据类型的真实信息;存放类实例,Java堆空间是最大的(需要存放实例),若Java堆空间不足,抛出异常:OutOfMemoryError; - 方法区
又称静态区,被装载的class文件存储在方法区内存中,线程共享,存放类信息、常量和静态变量;当虚拟机装载某个类型时,使用类装载器定位对应的class文件,然后读入该class文件内容(包含所有的class和static变量,方法区拥有一个常量池,String字符串等常量存储在常量池中),并传送到虚拟机中; - Java栈(虚拟机栈)
虚拟机只会直接对Java栈执行两种操作:以帧为单位压栈或出栈,线程私有,栈内容由一个个栈帧组成,每个正在执行的方法对应一个栈帧,当一个方法运行到一般需要调用另一个方法时,会创建一个新的栈帧表示新调用的方法,将原来的方法压入栈中,当方法运行完毕,栈帧出栈,原来方法处于栈顶接着运行;生命周期与线程相同,一个线程对应一个Java栈,每执行一个方法,将一个元素压入栈中,这个元素称为“栈帧”,栈帧中包括方法中的局部变量、存放中间状态值的操作站,若Java栈空间不足,排除异常:StackOverflowError(递归执行大量方法,可能导致栈数据溢出);
帧:由局部变量区(包括方法参数和局部变量,对于instance方法,要首先保存this类型,其中,方法参数按照声明顺序严格放置,局部变量可以任意放置)、操作数栈、帧数据区(帮助支持常量池的解析,正常方法返回和异常处理); - 程序计数器
每个线程都有各自的程序计数器(PC)寄存器,控制程序运行的顺序,在线程启动时创建,PC寄存器的内容总是指向下一条将被执行指令地址,这个地址可以是本地指针,亦可以是方法区中相对于该方法起始指令的偏移量; - 本地方法栈
保存本地方法进入区域的地址,使用C或C++实现,类似于Java栈,用来表示执行本地方法,本地方法栈存放的方法调用本地方法接口,最终调用本地方法库,实现与操作系统、硬件的交互。
3 Java代码编译过程
Java源码编译后文件主要分为两部分:常量池和方法字节码,所以在类加载时,将class文件加载到方法区(方法区包含常量池)。
即时编译器(Just In Time Compiler,JIT),针对解释型语言而言,并非虚拟机必须,是一种优化手段,Java的商用虚拟机HotSpot就有这种技术手段,Java虚拟机标准对JIT的存在并没有作出任何规范,所以,JIT是虚拟机实现的自定义优化技术。HotSpot虚拟机的执行引擎在执行Java代码时可以采用解释方式和编译方式执行,若采用编译执行,则会调用JIT,而解释执行不会使用JIT,因此,早期说Java是解释型语言,而在有JIT的Java虚拟机环境下,不可将Java简单归为解释型语言。HotSpot的编译器为javac,将*.java文件编译成字节码,这部分工作是独立完成的,不需要运行时参与,所以Java程序的编译时半独立实现的,通过字节码,就可通过解释器进行解释执行,这是早期的虚拟机工作流程,后来,虚拟机会将执行频率高的方法或语句通过JIT编译成本地机器码,提高了代码的执行效率。
4 JVM运行过程
编译器(如maven将*.java打包为jar或war)将*.java源文件编译为*.class字节码,JVM通过载入字节码执行程序,具体步骤如下:
- (1)方法区:
类加载器加载外存(如机械硬盘、SSD)字节码文件,将静态变量、静态方法、常量池及类代码加载到方法区; - (2)Java堆:
在Java堆中生成对应的类(反射)对象(java.lang.Class对象);使用二级缓存,需要手动释放缓存; - (3)Java栈:
执行方法时,在Java栈中生成栈帧,调用不同方法,一个线程一个Java栈,线程私有;使用一级缓存,用完即自动释放缓存; - (4)程序计数器:
程序执行的控制器,记录字节码指令(如指令、分支、循环等),一个线程一个程序计数器,线程私有; - (5)执行引擎:
执行引擎首先按照解释执行方式执行字节码,适当的时候,即时编译器将整段字节码编译为本地代码,此时,执行引擎不去解释执行,编译后的本地代码保存在缓存中,可以直接通过本地代码执行,执行速度更快,效率更高;
【参考文献】
[1]https://www.cnblogs.com/eastday/p/8124580.html
[2]https://www.jianshu.com/p/ee4a27f0e2f0
[3]https://blog.csdn.net/cheidou123/article/details/95054669
[4]https://www.cnblogs.com/czwbig/p/11127159.html
[5]https://www.cnblogs.com/hexinwei1/p/9406239.html
[6]https://blog.csdn.net/qq_36042506/article/details/82976586
[7]https://www.cnblogs.com/superyc/p/9987793.html
[8]https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc
[9]https://blog.csdn.net/Myuhua/article/details/81301205
[10]https://www.cnblogs.com/danyuzhu11/p/10767678.html
[11]http://ifeve.com/jvm-runtime-data/
[12]https://www.cnblogs.com/chenpt/p/8953435.html
[13]https://baijiahao.baidu.com/s?id=1665211762779262102&wfr=spider&for=pc
[14]https://blog.csdn.net/qq_40986486/article/details/106825285?%3E
[15]https://www.cnblogs.com/wade-luffy/p/5813747.html
[16]https://www.cnblogs.com/Timeouting-Study/p/12512774.html
[17]https://blog.csdn.net/csdnliuxin123524/article/details/81303711
[18]https://www.cnblogs.com/hujinshui/p/10398958.html
[19]https://www.cnblogs.com/yuechuan/p/8984262.html