JVM 体系结构 🔧🏗️
JVM(Java 虚拟机)可以被看作一个运行 Java 程序的工厂🏭,而这个工厂有几个主要部门,各司其职,让程序顺利运行。我们可以把 JVM 体系结构分为三个核心部分:类加载子系统、运行时数据区 和 执行引擎。每个部分都有它的独特职责。🤓📚
1. 类加载子系统(Class Loader Subsystem)🛠️
想象一下,你要做一道菜🍝,首先需要准备食材对吧?这个“类加载子系统”就是负责把你写的 Java 类(比如:HelloWorld.class
)加载到 JVM 中,就像把所有的食材搬到厨房一样。
- 工作方式:JVM 会通过类加载器找到并加载需要的 Java 类。首先会找到
.class
文件,然后把它加载到内存中准备执行。
2. 运行时数据区(Runtime Data Area)🗂️🧠
这里可以看作是 JVM 的“大脑”,负责管理和存储程序运行时所需的数据。想象一下你在做菜时需要用到不同的锅、碗、盘子🛒——这些工具就是 JVM 的内存区域,每个部分都有不同的功能。
- 堆(Heap):用来存储所有的对象和实例。就像你在厨房的冰箱里存放食材🍎🥦,所有的对象都放在这里。
- 栈(Stack):保存局部变量和方法调用。就像你做菜时用的刀具和锅具,它们都是临时用完就会放回去的🔪🍳。
- 方法区(Method Area):存储类结构、方法数据以及静态变量。这个就像你厨房的菜单📝,告诉你有哪些菜(类)以及这些菜怎么做(方法)。
- 程序计数器(Program Counter, PC):负责记录当前执行到哪一行代码了,像是你的烹饪指南📖,告诉你下一步该做什么。
- 本地方法栈(Native Method Stack):如果你的程序中用了其他语言(比如 C 或 C++),这里就是帮你运行这些本地代码的区域,就像你借用其他厨师的工具来帮你做菜🧑🍳。
3. 执行引擎(Execution Engine)🚀
现在你已经把食材准备好了,锅碗瓢盆都齐了,现在需要有人来烹饪对吧?执行引擎就是那个真正负责“做饭”的厨师👨🍳!它会根据你写的代码,把这些数据转换成机器可以理解的指令,让程序顺利运行。
- 解释器(Interpreter):逐行解释字节码(像是食谱📖),一行一行执行代码。
- 即时编译器(JIT Compiler):如果某些代码执行得特别频繁,JIT 会把这些代码直接编译成机器码,这样后续运行速度会更快!相当于提前准备好了一些食材,减少了重复劳动💨。
类加载器(Class Loader)🛠️🍕
类加载器就像厨房里的食材搬运员🍲,负责把你需要的“食材”(Java 类文件)搬进来,让 JVM 能处理这些类。每当你在 Java 里写一个类,JVM 都需要先找到这些类文件并加载到内存里才能执行。类加载器就是干这个活儿的。
类加载器的三种类型 🚚🔍
JVM 有三种常见的类加载器,想象它们是负责搬运不同层次的“食材员”:
- 启动类加载器(Bootstrap ClassLoader) 🏗️
- 它是大厨手下最有经验的搬运工💪,负责搬运最基础的食材,比如 Java 核心库的类文件(如
java.lang.String
)。这些食材非常基础,就像盐、油、糖这些每道菜都必须用的东西🧂。
- 它是大厨手下最有经验的搬运工💪,负责搬运最基础的食材,比如 Java 核心库的类文件(如
- 扩展类加载器(Extension ClassLoader) 📦
- 这个搬运工负责搬运扩展的食材🔧,也就是放在
ext
目录下的库文件。它会加载一些系统扩展库,就像是你准备了各种调料和香料🌶️,增加更多的功能。
- 这个搬运工负责搬运扩展的食材🔧,也就是放在
- 应用程序类加载器(Application ClassLoader) 🍔
- 这个搬运工专门负责加载我们自己写的代码📜,它会从你的项目路径下找到 Java 类文件。这就像是你把自己的菜谱交给搬运员,他帮你找到了特定的食材🥕🍗,然后带进厨房。
类加载的工作流程 📦➡️💻
- 首先,JVM 启动时,它会先用启动类加载器把最基本的 Java 类加载进来,比如
java.lang.Object
(就像做饭你总得先有锅有火🔥)。 - 然后,扩展类加载器会加载一些附加功能,比如图形界面、网络功能等(相当于加点香料,让菜更有味道🌶️)。
- 最后,应用程序类加载器会加载你自己写的类文件,这就是你定义的业务逻辑部分。
双亲委派机制(Parents Delegation Model)👪🚚
现在重点来了!✨双亲委派机制可以看作是类加载器的工作方式。它确保每个类加载器都不会自己“偷偷摸摸”去加载类文件,而是先请它的“父母”加载。🧓👦
解释方式: 假设你在厨房做饭,你先让大厨手下最有经验的搬运工去找食材🍅,如果他找不到,才会轮到下一个搬运工。每个类加载器在加载类时,都会先请求它的“父母”类加载器看看是否已经加载过这个类。
双亲委派的步骤:
- 如果你要加载一个类,应用程序类加载器不会直接去找,而是先问扩展类加载器:“你有没有这个类啊?”🤔
- 扩展类加载器也不会马上去找,而是继续问启动类加载器:“你有没有这个类?”🤔
- 启动类加载器会先尝试加载,如果找不到,权限才会返回给下一个类加载器,直到应用程序类加载器自己加载。
这个机制确保了 Java 核心类库不会被随便“篡改”。比如,如果你写了一个自己的 java.lang.String
类,双亲委派机制会让启动类加载器优先加载官方的 String
类,而不是你自定义的。👨🏫💡
双亲委派机制的现实类比 🍔👩👩👦
让我们用一个简单的生活类比来帮助你更好地理解这个机制:
想象你是一家餐厅的经理🍽️,有顾客点了一份汉堡🍔。你不会自己去厨房亲自做汉堡,而是会先问你最老练的大厨:“你知道怎么做这个汉堡吗?”🍳
- 如果大厨知道怎么做,那一切搞定!🍔😋
- 如果大厨不知道,他会去问负责配菜的小厨师👩🍳。
- 最后,如果所有大厨都不懂,那你才会自己动手去做汉堡。
这个流程就是双亲委派机制,它确保程序中的类不会被误加载,先从最核心的地方找,找不到才会逐层往下找,直到最后一层。
总结 💡🎯
- 类加载器:它是 JVM 中的搬运员,负责把类文件(Java 类)加载进来。
- 双亲委派机制:它确保每个类加载器不会直接加载类,而是先让“父母”类加载器尝试加载,避免重复或篡改类。