JVM入门到入土-Java虚拟机概述与JAVA代码执行过程
虚拟机与JVM的概念与差异
虚拟机(Virtual Machine,VM)是一种软件或硬件实现的仿真系统,它可以在一个计算环境中模拟另一个计算环境。虚拟机的目的是提供一个隔离的、独立的运行环境,使得应用程序或操作系统可以在其中运行,而不受底层硬件或操作系统的影响。
在物理层面和软件层面,虚拟机有不同的概念和实现方式。
-
物理层面虚拟机:
- 在物理层面,虚拟机通常是通过虚拟化技术实现的,这种技术允许在一台物理计算机上运行多个虚拟机,每个虚拟机都像一个独立的物理计算机一样工作。
- 物理层面虚拟机的目标是将物理硬件资源(如CPU、内存、存储)划分和共享,使多个操作系统或应用程序能够并行运行,而彼此之间相互隔离。
- 例子包括使用虚拟化软件(如VMware、Hyper-V)创建的虚拟机,这些虚拟机可以运行不同的操作系统,如Windows、Linux等。
-
软件层面虚拟机(以JVM为例):
- 在软件层面,虚拟机通常是一个软件程序,它在主机操作系统上运行,并提供一个抽象层,使得应用程序可以在其上运行,而不受主机操作系统的直接影响。
- Java虚拟机(JVM)是一个常见的软件层面虚拟机的例子。JVM允许Java程序在任何支持JVM的平台上运行,而不需要重新编写代码。JVM将Java字节码翻译成特定平台的机器码,实现了跨平台的特性。
- 软件层面虚拟机的主要目标是提供一个独立于硬件和操作系统的运行环境,使得应用程序能够在不同的平台上具有可移植性。
区别:
- 层次不同: 物理层面虚拟机操作在硬件层面,通过虚拟化技术划分和共享物理资源;软件层面虚拟机操作在应用层面,提供一个虚拟的运行环境。
- 目标不同: 物理层面虚拟机的目标是在一台物理计算机上运行多个操作系统或应用程序,实现资源的隔离和共享;软件层面虚拟机的目标是提供一个独立于硬件和操作系统的运行环境,使得应用程序具有可移植性。
- 实现方式不同: 物理层面虚拟机通过虚拟化技术实现,如全虚拟化或半虚拟化;软件层面虚拟机通过解释或即时编译等技术将高级语言代码转换成目标平台的机器码。
JVM的发展历程
Java Virtual Machine(JVM)是Java程序的运行环境,它使得Java程序能够在不同的平台上运行。以下是JVM的发展历程的主要阶段:
-
JVM的初期阶段(1996年):
- 最初的JVM是由Sun Microsystems设计并实现的,于1996年发布。
- 该版本的JVM主要用于支持Java 1.0版本。
-
Java 2发布(1998年):
- Java 2发布时引入了很多新特性,也对JVM进行了升级。
- 引入了新的垃圾回收器(Garbage Collector)和即时编译器(Just-In-Time Compiler,JIT)等性能优化工具。
-
HotSpot JVM(1999年):
- JDK 1.3引入了HotSpot JVM,它是Sun Microsystems对JVM的性能进行重大改进的产物。
- HotSpot JVM引入了即时编译器,用于在运行时将Java字节码编译成本地机器代码,从而提高程序的执行速度。
-
Java 5发布(2004年):
- 引入了新的JVM特性,包括Java虚拟机堆的分代垃圾回收器(Generational Garbage Collector)和Java虚拟机调优工具。
- 也引入了重要的语言特性,如泛型、枚举、注解等。
-
Java 7发布(2011年):
- Java 7引入了新的垃圾回收器(G1 Garbage Collector)和NIO 2.0(New I/O)等特性。
- 对于JVM的性能进行了进一步的优化。
-
Java 8发布(2014年):
- 引入了重要的语言特性,如Lambda表达式和Stream API。
- JVM中的PermGen(永久代)被移除,取而代之的是Metaspace。
-
Java 9发布(2017年):
- 引入了模块化系统,将JVM本身划分为多个模块。
- 提供了JShell(交互式编程工具)和对HTTP/2的支持。
-
Java 11发布(2018年):
- 引入了Z Garbage Collector,用于改进垃圾回收性能。
- 移除了Java EE模块,使得Java EE成为独立的项目(现在的Jakarta EE)。
-
Java 14、15、16、17等版本(2020年及以后):
- 持续对JVM进行改进和优化,引入了一些新的特性和性能提升。
- 例如,Java 14引入了JEP 358,支持非标准的Switch表达式,Java 15引入了JEP 377,改进了垃圾回收器。
JVM的架构层次
JVM建立在操作系统之上,没有与物理硬件的直接交互
JVM的作用
Java虚拟机(JVM)是Java编程语言的运行环境,它扮演着将Java源代码转换为可执行代码的关键角色。JVM执行的是Java字节码,这是一种中间代码,位于Java源代码和本地机器代码之间。从字节码的角度来看JVM的作用:
字节码执行: JVM的主要作用之一是执行Java字节码。在Java编译过程中,源代码首先被编译成字节码,而不是直接编译成本地机器代码。这个字节码是一种与平台无关的中间表示形式,可以在任何支持JVM的平台上执行。
平台独立性: 由于Java程序被编译成字节码而不是特定于某个操作系统的机器码,JVM提供了平台独立性。字节码可以在任何装有JVM的系统上运行,只要这个系统的JVM实现了Java虚拟机规范。
即时编译(Just-In-Time Compilation,JIT): JVM包含即时编译器,可以将字节码动态地编译成本地机器码,以提高程序的执行性能。即时编译器将频繁执行的字节码转换为本地机器代码,避免了每次都解释字节码的性能开销。
垃圾回收: JVM负责自动内存管理,其中一个重要的组成部分是垃圾回收器。垃圾回收器负责回收不再被引用的对象,释放内存,避免了手动内存管理的复杂性。
安全性: JVM提供了一些安全机制,例如字节码验证,以确保字节码是合法且安全的。这有助于防止一些潜在的安全漏洞,使得Java应用程序更加可信赖。
动态性: Java是一种动态语言,而JVM通过支持动态类加载和运行时反射等特性,使得Java程序可以在运行时动态地加载、链接和执行类。
简而言之:
JVM就是二进制字节码的运行环境,负责将字节码加载到其内部,将其解释/编译为对应平台的机器指令来执行,它具有以下特点:
- 一次编译,到处执行
- 自动内存管理
- 自动垃圾回收
注意:现在的JVM已经支持运行其他语言的,符合JVM规范的字节码(也称为JVM字节码)
JVM的结构
注意:方法区与堆区多线程共享,而虚拟机栈,方法栈,寄存器各线程独自持有一份
Java编译器的任务
在Java编程语言中,编译过程通常包括前端编译器和后端编译器,这两个阶段协同工作以将源代码转换为可执行的目标代码:
前端编译器(Frontend Compiler):
-
- 前端编译器主要负责处理源代码,并将其转换为中间代码,即Java字节码。这个阶段包括词法分析、语法分析、语义分析等步骤,以便生成一个中间表示形式,即抽象语法树(Abstract Syntax Tree,AST)。
- 在Java编译过程中,javac是负责执行前端编译的主要工具。javac接收Java源代码文件作为输入,然后进行词法和语法分析,解析语法结构,进行类型检查和生成字节码。生成的字节码是一种与平台无关的中间表示,它包含了程序的逻辑结构和操作指令。
-
后端编译器(Backend Compiler):
- 后端编译器负责将生成的字节码转换为目标平台的本地机器码,使得程序可以在特定的硬件和操作系统上执行。这个阶段通常包括优化、即时编译(Just-In-Time Compilation,JIT Compilation)等步骤。
- Java虚拟机(JVM)中的即时编译器(JIT Compiler)是后端编译器的一部分。当Java程序运行时,JVM会动态地将字节码翻译成本地机器码,从而提高程序的执行性能。这种即时编译的方式避免了解释执行的性能瓶颈,同时充分利用了运行时的信息来进行优化。
Java的前端编译器负责将源代码转换为中间代码(字节码),而后端编译器负责将字节码转换为目标平台的本地机器码。这种分层的编译模型使得Java具有平台独立性,同时在运行时可以获得较好的性能。前端编译器和后端编译器的协同工作使得Java语言得以在不同平台上实现**“一次编写,到处运行”**的理念。
JAVA代码怎样被执行
Java源码的执行过程如上,我们接下来描述每一个层次:
源码to字节码
字节码to机器指令
运行时数据区-执行引擎行为
在此过程中还有几大行为:
- 后端编译器:直接翻译字节码进行直接地执行以保证即时响应功能
- JIT即时编译器:将常用的热点代码二次编译(一次编译为前端编译器产生class文件,即Java字节码)为机器码,缓存至方法区,以保证性能提升特性
- 垃圾回收器: 主要任务是管理和回收内存。Java的垃圾回收器追踪程序中的对象引用,找出不再被程序使用的对象,并释放其占用的内存,即未使用的类将可能,永远不会被加载,降低内存泄漏风险
类加载子系统Class对象和类关联Class类比较
与类关联的Class类
在Java中,每个类都与一个Class
类相关联。Class
类是Java反射机制的核心部分,它提供了许多方法,允许在运行时获取有关类的信息,创建类的实例并访问类的成员。
以下是Class
类的一些常见用途和方法:
- 获取Class对象:
- 通过对象的
getClass()
方法:Class<?> clazz = obj.getClass();
- 通过类的
.class
属性:Class<?> clazz = MyClass.class;
- 通过Class类的
forName
方法:Class<?> clazz = Class.forName("fully.qualified.ClassName");
- 通过对象的
- 获取类的信息:
getName()
:获取类的全限定名。getSimpleName()
:获取类的简单名称。getModifiers()
:获取类的修饰符。getPackage()
:获取类所在的包。
- 创建实例:
newInstance()
:创建类的实例。注意,这是一种过时的方法,推荐使用Constructor
类的newInstance
方法。
- 获取类的成员:
getFields()
:获取类的公有字段。getDeclaredFields()
:获取类的所有字段,包括私有字段。getMethods()
:获取类的公有方法。getDeclaredMethods()
:获取类的所有方法,包括私有方法。getConstructors()
:获取类的公有构造方法。getDeclaredConstructors()
:获取类的所有构造方法,包括私有构造方法。
- 操作类的成员:
getField(String name)
:获取指定名称的公有字段。getDeclaredField(String name)
:获取指定名称的字段,包括私有字段。getMethod(String name, Class<?>... parameterTypes)
:获取指定名称和参数类型的公有方法。getDeclaredMethod(String name, Class<?>... parameterTypes)
:获取指定名称和参数类型的方法,包括私有方法。
- 其他方法:
isInstance(Object obj)
:判断指定的对象是否与当前Class表示的类或接口兼容。isArray()
:判断是否为数组类。isInterface()
:判断是否为接口。isEnum()
:判断是否为枚举类型。
类加载子系统的Class对象
在Java虚拟机(JVM)中,类加载子系统负责加载类并生成对应的Class
对象。当Java程序启动时,JVM会初始化一个类加载器(ClassLoader),并在运行过程中动态加载类。类加载子系统主要分为三个阶段:加载(Loading)、连接(Linking)、初始化(Initialization)。
-
加载(Loading):
在加载阶段,类加载器通过查找类的字节码文件(通常是.class文件)并创建一个Class
对象。这个Class
对象包含了类的结构信息,如字段、方法、构造器等。 -
连接(Linking):
连接阶段包括三个子阶段:验证(Verification)、准备(Preparation)、解析(Resolution)。- 验证(Verification): 确保类的字节码符合JVM规范,不会导致安全问题。
- 准备(Preparation): 为类的静态变量分配内存,并初始化为默认值。
- 解析(Resolution): 将类、接口、字段和方法的符号引用解析为直接引用。
-
初始化(Initialization):
在这个阶段,JVM执行类的初始化代码,包括执行静态初始化块和静态变量的赋值。初始化是类加载的最后一个阶段,它标志着类准备好被使用。
当类加载子系统完成这些阶段后,就生成了对应的Class
对象,这个对象包含了类的所有信息,可以通过反射来访问和操作类的成员。
Class<?> clazz = MyClass.class;
需要注意的是,类加载是在运行时动态进行的,而Class
对象的生成也是在类加载的过程中完成的。每个类在JVM中只有一个Class
对象,无论这个类被加载多少次。这个Class
对象在内存中存储了该类的结构信息,可以在程序运行时通过反射机制进行访问。
二者异同
类加载子系统产生的Class
对象和通过反射机制获取的Class
对象之间的主要异同:
相同点:
- 共同目标: 无论是类加载子系统产生的
Class
对象还是通过反射获取的Class
对象,它们都代表了相同的Java类或接口。 - 包含相似信息: 两者都包含了类的结构信息,包括字段、方法、构造器等。
不同点:
-
产生方式:
- 类加载子系统:
Class
对象是在类加载过程中由类加载器负责生成的。类加载器在加载、连接和初始化阶段生成Class
对象,这是Java虚拟机的基本工作。 - 反射机制: 通过反射,可以在程序运行时动态获取和操作
Class
对象。这种方式不涉及类加载的过程,而是在已加载的类上进行反射操作。
- 类加载子系统:
-
时机:
- 类加载子系统:
Class
对象的生成是在类加载的过程中进行的,通常发生在程序启动时或在首次使用类的时候。 - 反射机制:
Class
对象的获取是在程序运行时通过反射 API 进行的,可以随时在程序中进行。
- 类加载子系统:
-
用途:
- 类加载子系统: 主要负责将类的字节码加载到内存,并进行一系列的连接和初始化工作。它是Java虚拟机的一部分,负责管理类的加载。
- 反射机制: 允许在运行时检查和操作类,可以动态地创建对象、调用方法、访问字段等。反射机制通常用于编写通用框架和工具,或者在运行时需要动态处理类的情况下使用。
-
性能:
- 类加载子系统: 类加载是一项较为底层的操作,直接涉及到类的字节码加载和初始化,可能在性能上更高效。
- 反射机制: 反射操作相对较为灵活,但可能在性能上略逊于直接的静态类型调用,因为它需要在运行时进行类型检查和方法调用。