JVM类运行机制实现原理解析
zi萱 人气:01.一段java程序是如何运行起来的呢?
Java源文件,通过编译器,产生.Class字节码文件,字节码文件通过Java虚拟机中的解释器,编译成特定及其上的机器码,那Java虚拟机又是怎样加载java程序并执行起来的呢?
简单来说:通过类加载器加载字节码文件,被分配到JVM的运行时数据区的字节码会被执行引擎执行。
(1)类加载器,加载.class文件
(2)运行数据区:栈区、堆区、PC寄存器、本地方法栈、方法区
(3)执行引擎:执行包在装载类方法中的指令
2. 类加载器
类的加载是指将类的.class文件读入内存,将其放在方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构,并向java程序员提供访问方法区内数据结构的接口。类加载器并不需要等到某个类被首次主动使用时再加载它,JVM允许类加载器在预料某个类将要被使用时就预先加载它。
类的生命周期
类加载过程包括:加载、验证、准备、解析、初始化
(1)加载:查找并加载类的二进制数据。
a. 通过一个类的全限定名来获取其定义的二进制字节流
b. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
c. 在Java堆中生成一个代表这个类的java.lang.Class,作为对方法区中这些数据的访问入口
(2)连接:
a. 验证:确保被加载类的正确性
b. 准备:为类的静态变量分配内存,并将其初始化为默认值
c. 解析:把类中的符号引用转换为直接引用
(3)初始化:为类的静态变量赋予正确的初始值
类加载器
启动类加载器:BootstrapClassLoader,负责加载存放在JDK\jre\lib或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库。
扩展类加载器:ExtensionClassLoader,负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库。
引用程序类加载器:ApplicationClassLoader,负责加载用户路径ClassPath所指定的类
JVM类加载机制
全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
父类委托:先让父类加载器驶入加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
缓存机制:保证所有加载过的类都会被缓存,当需要使用某个类时,先从缓存区寻找该Class,只有缓存区不存在该类时,才会去加载此类。
双亲委派机制
意义:系统类防止内存出出现多分同样的字节码;保证Java程序安全稳定运行
(1)当AppClassLoader去加载一个class时,它首先不会自己去加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
(2)当ExtClassLoader去加载一个class时,它首先也不会自己去加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
(3)如果BootStrapClassLoader加载失败,会使用ExtClassLoader来尝试加载。
(4)如果ExtClassLoader加载失败,会使用AppClassLoader来加载
(5)如果AppClassLoader也加载失败,则会爆出异常ClassNotFoundException
3. 运行数据区
(1)虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着一种叫“栈帧”的东东,每个方法会创建一个栈帧,栈帧中存放局部变量表(基本数据类型和对象饮用)、操作数栈、方法出口等信息,栈的大小可以固定也可以扩展,当栈调用深度大于JVM所允许的范围,会抛出StackOverFlowError。
(2)本地方法栈:主要与虚拟机用到的native方法相关,java程序员不太关心。
(3)PC寄存器:也叫程序计数器。JVM支持多线程运行,每个线程都有自己的程序计数器。若当前执行的是JVM的方法,则该寄存器中保存当前执行指令的地址,若执行native方法,则为空。
(4)堆:堆内存是JVM所有线程共享的部分,虚拟机启动时就已经创建。所有对象和数组都在堆上进行分配。这部分空间可通过GC进行回收,当申请不到空间时会抛出OutOfMemoryError。
(5)方法区:所有线程共享。主要用于存储类的信息,常量池,方法数据,方法代码等。
4. 执行引擎
方法调用会导致栈帧的入栈,会确定调用哪一个方法。
(1)栈帧。程序的执行对应着栈帧的入栈和出栈,栈帧主要包括:局部变量表、操作数栈、动态连接、方法返回地址等。
(2)方法调用。
解析调用:类加载的解析阶段,会将其中一部分符号引用转化为直接引用,这种解析的前提是方法在程序真正运行之前就有一个可确定的调用版本。编译期可确定调用方法的版本:静态方法、私有方法、实例构造器、父类方法。
分派调用:
a. 静态分派:发生在编译阶段。所有依赖于静态类型来定位方法执行版本的分派动作成为静态分派,典型方法是重载。javac编译器根据参数的静态类型决定使用哪个重载版本。
b. 动态分派:运行期根据实际类型确定方法执行版本。与方法重写有密切关系。
c. 单分派和多分派:单分派是根据一个宗量对目标方法进行选择,多分派是根据多于一个宗量对目标方法进行选择。
(3)执行引擎需将字节码转换成可以直接被JVM执行的语言,可通过以下两种方式转换:
a. 解释器:一条一条的读取,解释并且执行字节码指令
b. 即时编译器:执行引擎首先按照解释执行的方式来执行,在合适的时候,即时编译器把整段字节码编译成本地代码。内置了JIT编译器的JVM都会检查方法的执行频率,如果一个方法的执行频率超过一个特定的值的话,那么这个方法就会被编译成本地代码。
加载全部内容