1.整体架构和组件
1.Class Loader
Class Loader(类加载器)负责将.class文件加载到JVM中,并生成对应的Java类对象(Class对象)。Java中有三种类加载器:
- Bootstram ClassLoader:加载核心类库,使用C++实现,是JVM的一部分;
- Extension ClassLoader:加载Java扩展类库(例如javax.*包),使用Java实现,是sun.misc.Launcher的一部分;
- App ClassLoader:加载应用程序类,使用Java实现,是ClassLoader的子类。
.2.Execution Engine
Execution Engine(执行引擎)负责解释字节码,将其转换为机器指令,并执行程序。JVM的执行引擎包括两种模式:
- 解释器模式(Interpretive Mode):逐行解释字节码,并执行相应的机器指令。这种模式的优点是可以快速启动,但是执行速度较慢。
- 编译器模式(Compiler Mode):将字节码编译为本地机器指令,并执行编译后的代码。这种模式的优点是执行速度快,但是启动速度慢。
JVM采用了混合模式(Mixed Mode),在程序运行的过程中根据需要动态地选择使用解释器模式或编译器模式。
3.Runtime Data Area
Runtime Data Area(运行时数据区)是JVM中的数据存储区域,包括以下组件:
- Method Area(方法区):存储类信息、常量、静态变量等数据。
- Heap(堆):存储Java对象和数组。
- Stack(栈):存储Java方法的局部变量、操作数栈、方法出口等信息。
- PC Register(程序计数器):存储正在执行的Java方法的地址。
- Native Method Stack(本地方法栈):为Java调用本地方法提供支持。
4.Native Method Interface
Native Method Interface(本地方法接口)允许Java代码调用C或C++编写的本地方法。本地方法通过JNI(Java Native Interface)实现。JVM将Java参数转换为C/C++参数,调用本地方法并返回结果。
JVM的整体架构和组件是Java程序运行的基础,对于Java程序员来说,了解JVM的原理和内部机制可以帮助他们写出更高效、更稳定的Java程序。
2.组件间交互方式
2.1Class Loader和Method Area
当一个Java类第一次被加载时,Class Loader会将类的.class文件读入内存,并在Method Area中创建对应的Class对象,包括类的成员变量、方法等信息。在JVM运行期间,所有的类信息都保存在Method Area中,包括类的字节码、运行时常量池、字段和方法信息等。
2.2Heap和Stack
在Java程序中,创建对象时,它们的实例数据存储在Heap中,而对象的引用则存储在Stack中。每个Java方法都会创建一个栈帧(Stack Frame),存储该方法的局部变量、操作数栈、方法出口等信息,栈帧也存储在Stack中。
当一个Java方法被调用时,JVM会在Stack中创建一个新的栈帧,并将该栈帧推入栈顶。当方法执行完成后,JVM会弹出该栈帧,并回收其内存空间。
2.3Execution Engine和Method Area
Execution Engine负责执行Java字节码,并将其转换为计算机能够执行的机器指令。在执行字节码之前,Execution Engine需要在Method Area中查找字节码、常量等信息,并将其载入运行时常量池中。
2.4Native Method Interface和Native Method Stack
当Java程序需要调用C/C++编写的本地方法时,JVM会将Java参数转换为C/C++参数,并调用本地方法。本地方法的结果会被返回给Java程序,并且需要将C/C++结果转换为Java结果。
Native Method Interface提供了一种机制,可以在Java程序中调用C/C++编写的本地方法,使Java程序能够与底层系统交互。
3.类加载器的工作原理和类加载的过程
类加载器(ClassLoader)是JVM的一个重要组成部分,它负责将Java类从磁盘或网络等外部存储器中加载到JVM的内存中。类加载器采用的是“双亲委派模型”,即当一个类需要被加载时,它首先会委托它的父类加载器寻找该类,直到最终委托到启动类加载器为止。如果所有的父类加载器都无法找到该类,则由该类加载器自行加载。
类加载的过程通常包括以下三个阶段:
-
加载(Loading):查找并加载类的二进制数据。类加载器首先会通过类的全限定名找到对应的.class文件,然后将二进制数据读入内存,并在内存中创建对应的Class对象。需要注意的是,同一个类在JVM中只会被加载一次。
-
连接(Linking):将类的二进制数据合并到JVM的运行时环境中。连接阶段包括三个步骤:
- 验证(Verification):确保类的二进制数据符合JVM规范,并且没有安全漏洞。
- 准备(Preparation):为类的静态变量分配内存,并设置默认值。
- 解析(Resolution):将符号引用替换为直接引用,即将类、字段、方法等引用转换为内存地址。
- 初始化(Initialization):为类的静态变量赋初值,并执行类的静态代码块。在JVM中,类的初始化是一个线程安全的操作,保证了类只会被初始化一次。如果该类还有父类,那么会先初始化父类。
需要注意的是,JVM只有在需要用到某个类时才会进行类的加载和初始化,这种机制被称为“延迟加载”。同时,JVM还支持动态类加载机制,可以在程序运行期间通过Java反射机制加载新的类,并将其加入到JVM中。
4.字节码指令集的基本语法和用法
Java代码在经过编译器编译后,会被转换成字节码(Bytecode),也就是一种跨平台的中间代码。字节码指令集是Java虚拟机(JVM)可以识别并执行的代码格式。字节码指令集具有简洁、紧凑的特点,并且与底层的硬件架构无关,因此可以在不同的平台上运行。
字节码指令集由单个字节的指令组成,每个指令都有一个操作码(Opcode)和一个或多个操作数。Java虚拟机通过执行一系列的字节码指令来完成Java程序的运行。
下面是字节码指令集的一些基本语法和用法:
4.1加载和存储指令
- 从局部变量表加载值到操作数栈:iload, dload, aload, etc.
- 从操作数栈存储值到局部变量表:istore, dstore, astore, etc.
4.2运算指令
- 二进制运算指令:iadd, dadd, isub, dsub, imul, dmul, idiv, ddiv, irem, drem, etc.
- 位运算指令:ishl, ishr, iushr, iand, ior, ixor, etc.
4.3类型转换指令
- 将整型值转换为其他类型:i2d, i2l, i2f, etc.
- 将浮点型值转换为其他类型:d2i, d2l, d2f, etc.
- 将长整型值转换为其他类型:l2i, l2d, l2f, etc.
4.4控制指令
- 条件跳转指令:ifeq, ifne, iflt, ifgt, ifle, ifge, etc.
- 无条件跳转指令:goto, goto_w, etc.
- 返回指令:ireturn, dreturn, areturn, etc.
4.5对象操作指令
- 创建新对象指令:new, newarray, anewarray, etc.
- 字段操作指令:getfield, putfield, getstatic, putstatic, etc.
- 方法调用指令:invokevirtual, invokespecial, invokestatic, invokeinterface, etc.
总的来说,字节码指令集提供了Java虚拟机执行Java程序的基本语法和用法,同时也是Java程序跨平台运行的重要保障之一。
5.JIT编译器和AOT编译器
JIT编译器和AOT编译器都是将代码转换为机器码的工具,但它们的工作方式和优缺点存在较大差异。
JIT编译器(Just-In-Time Compiler)在程序运行过程中,将字节码实时编译为本地机器码执行。JIT编译器可以根据程序的实际运行情况,对热点代码进行优化,提高程序的执行效率。JIT编译器的优点包括:
- 即时编译,避免了预编译导致的启动时间过长问题;
- 动态编译,可以根据程序的实际运行情况进行优化,提高程序的执行效率;
- 与Java虚拟机紧密结合,提高了程序的可移植性和兼容性。
JIT编译器的缺点包括:
- 编译时间较长,可能会影响程序的响应时间;
- 对于一些只执行一次的代码,JIT编译器不会进行优化,浪费了一些性能资源;
- JIT编译器会占用较多的内存空间。
AOT编译器(Ahead-Of-Time Compiler)在程序运行前,将Java字节码编译为本地机器码,生成可执行文件。AOT编译器可以通过静态编译的方式,对整个程序进行优化,提高程序的执行效率。AOT编译器的优点包括:
- 编译时间较短,启动时间较短;
- 可以进行全局优化,对整个程序进行优化,提高程序的执行效率;
- 可以在没有Java虚拟机的环境下运行程序。
AOT编译器的缺点包括:
- 缺乏动态优化,对于程序的实际运行情况无法进行优化;
- 可能导致可移植性和兼容性问题;
- 占用磁盘空间较大,难以适用于资源受限的环境。
综上所述,JIT编译器和AOT编译器在实现方式和优缺点上存在差异,各自适用于不同的场景。在Java虚拟机中,JIT编译器是主流的编译器,可以提供动态优化和更好的可移植性;而AOT编译器则更适用于一些特定场景,如嵌入式系统或移动端应用。
欢迎大家访问:http://mumuxi.chat/