大家好,我是程序员大猩猩。
今天我们来讲一下JVM,好多面试者在面试的时候,都会被问及JVM相关知识。那么JVM到底是什么,要理解它到底是出于什么原因?
JVM俗称Java虚拟机,它是一个抽象的计算机,是提供一个可以运行java程序的软件环境。
JVM主要的任务是加载字节码文件(Java文件编译后的.class文件)、验证字节码、执行字节码。
首先看看JVM的结构模型:
-
类加载器(Class Loader)
-
负责将.class文件加载到JVM中
-
JVM预定义了三个类加载器:Bootstrap Class Loader、Extension Class Loader和System Class Loader。
-
可以自定义类加载器。
如何自定义类加载器呢? 重新ClassLoader类并重写了findClass方法
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// ....
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
CustomClassLoader loader = new CustomClassLoader();
Class<?> clazz = loader.findClass("com.example.MyClass");
Object instance = clazz.newInstance();
System.out.println("Loaded class: " + instance.getClass().getCanonicalName());
}
}
那么实现自定义类加载器可以用来干什么呢?
-
实现热部署,即在运行时重新加载类的修改版本。
-
加密类文件,然后在加载时解密。
-
从非标准位置(如数据库、网络或其他专用格式)加载类文件。
2. 运行时数据区(Runtime Data Area):
-
方法区(Method Area)
1. 被所有线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
2. 当方法区无法满足内存分配需求时,将抛出
OutOfMemoryError
异常。 -
堆(Heap)
1. Java虚拟机管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建。
2. 唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
3. 垃圾回收器管理的主要区域,因此也被称作“GC堆”
-
栈(Stack)
1. 每个线程创建时都会创建一个虚拟机栈,其生命周期与线程相同。
2. 虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行时,都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
3. 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向代表对象的句柄或其他与此对象相关的位置)和返回地址。
-
程序计数器(Program Counter Register)
1. 存储当前线程执行的字节码的地址。每个线程都有一个程序计数器,是线程私有的。
2. 执行Java方法时,程序计数器记录的是正在执行的虚拟机字节码指令的地址。如果是本地方法,程序计数器的值则为空(Undefined)。
-
本地方法栈(Native Method Stack)
1. 为使用Java Native Interface(JNI)的本地方法服务。
2. Native方法栈也会在栈深度溢出或栈扩展失败时抛出StackOverflowError和OutOfMemoryError异常。
3. 执行引擎(Execution Engine):
-
字节码加载:
-
类加载器将
.class
文件加载到JVM的方法区。 -
执行引擎通过方法区的字节码来执行程序。
-
-
解释执行:
-
字节码最初是通过解释器逐条解释执行的。
-
解释器快速地将字节码转换成对应的机器指令,然后执行。
-
-
即时编译(Just-In-Time Compilation,JIT):
-
为了提高执行效率,现代JVM通常包含一个即时编译器(JIT编译器)。
-
JIT编译器将热点代码(即执行频率高的代码)编译成机器码,并将其存储起来,以便后续可以直接执行编译后的机器码,而不是再次解释执行。
-
这种混合使用解释执行和编译执行的方法称为“即时编译”。
-
-
优化:
-
JIT编译器在编译字节码时,会进行各种优化,如方法内联、逃逸分析、循环展开等,以提高执行效率。
-
4.本地库接口(Native Interface):
-
执行引擎支持JNI,允许Java代码调用其他语言编写的本地库(如C/C++库)。
-
通过JNI,Java程序可以执行本地方法,这些方法通常是用C/C++等语言编写的,并编译成了本地机器码。
5.垃圾回收器(Garbage Collector):
-
负责自动管理内存,回收不再使用的对象占用的内存。
-
减轻了开发者手动管理内存的负担。
-
执行引擎与垃圾回收器紧密合作,确保在执行过程中,不再使用的对象可以被及时回收,从而释放内存资源。
6.本地方法库(Native Method Libraries):
-
提供了一系列本地方法,供Java程序调用。
-
这些方法通常是用C/C++等语言编写的。
JVM的设计允许Java程序在不同的平台上运行,而无需为每个平台编译一个版本。
Java程序首先被编译成字节码,然后由JVM在各个平台上解释执行或编译执行。
这使得Java语言具有“一次编写,到处运行”(Write Once, Run Anywhere)的特性。