JVM原理及其机制(一)

发布于:2025-07-27 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

一 . 什么是 JVM 

JVM 相较于其他两个虚拟机的区别:

二 . JVM 的内存区域划分 

(1)程序计数器 

(2)堆 

(3)栈 

(4)元数据区(也叫方法区)

三 . JVM 的类加载过程 

四 . 类加载的具体步骤 

(1)加载 

(2)验证 

(3)准备 

(4)解析 

(5)初始化 

五 . 双亲委派模型 

(1)类加载器 

(2)双亲委派模型的具体工作流程:


一 . 什么是 JVM 

JVM 就是 Java Virtual Machine 的简称,意思就是 Java 虚拟机。虚拟机指的就是通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整的计算机系统。

常见的虚拟机:JVM、VMwave、Virtual Box 等等。 

JVM 相较于其他两个虚拟机的区别:

(1)VMwave 和 Virtual Box 是通过软件模拟物理 CPU 的指令集,物理系统中会有很多寄存器。

(2)JVM 则是通过软件模拟 Java 字节码的指令集,JVM 中只是保留了 PC 的寄存器,其他寄存器都进行了裁剪。( JVM 是一台被定制过的,现实当中不存在的计算机)

二 . JVM 的内存区域划分 

(1)程序计数器 

程序计数器是一个比较小的空间,它的作用只有一个:保存了下一条要执行的指令的地址。注意,程序计数器不是 CPU 的寄存器,而是内存空间,这里 “ 下一条要执行的指令 ” 是 Java 的字节码,不是 CPU 的二进制的机器语言。

(2)堆 

“ 堆 ” 这块空间可以认为是我们整个 JVM 上最大的空间。但凡是我们 new 出来的对象都在堆上。

注意:堆中放置的是成员变量。

(3)栈 

在栈中主要保存的信息就是我们函数中的局部变量,函数的形参,函数之间的调用关系。

在栈中还分为 Java 虚拟机栈,本地方法栈,Java 虚拟机栈就是在 JVM 上层,运行的 Java 代码的方法调用关系。本地方法栈中就是在 JVM 里面,C++ 代码的函数调用关系。

注意:栈中放置的是局部变量。

(4)元数据区(也叫方法区)

储存的 Java 程序中的一些指令(指令都是包含在类的方法中的)。所以也可以说在元数据区中保存了代码中的涉及到类的相关信息,还有类的 static 属性。

注意:在一个 Java 进程中,元数据区和堆有且只有一份(同一个进程中的所有线程,都是共用同一份数据),而程序计数器和栈,则有可能有多份(当一个 Java 进程有多个线程的时候,每个线程都有自己的程序计数器和栈)。

线程就表示一个 “ 执行流 ”,每个线程就需要保护自己的 “ 程序计数器 ”,每个线程也需要记录自己的调用关系。

注意:元数据区中放置的是静态成员变量。

三 . JVM 的类加载过程 

什么是类加载过程呢?例如我们当前写的一个 Java 程序,就得到了一个 “ .java 文件 ” ,然后我们通过 Javac 进行编译,得到了一个 “ .class 文件 ”(文件就属于是在硬盘上),当编译完成,就应该运行了,当我们运行 Java 进程的时候,此时 JVM 就需要去读取 “ .class ” 中的内容,并且执行其中的指令。

在这之中,“ 读取 “ .class ” 中的内容 ” 这一操作就是我们的 “ 类加载 ” :把类涉及到的字节码,从硬盘读取到内存(元数据区)中。

放到内存之中又是怎样运行组织的呢?就会把这个 “ .class ” 中的指令转变成一个 “ 类对象 ” 。有了类对象,我们才能进行反射。反射的各种 API 都是从类对象中拿到信息的。

加载一个 “ .class 文件 ” 也就会对应创建一个 “ 类对象 ” ,这个类对象中就包含了 .class 文件中的各种信息。比如:类的名字?类的属性?每个属性的名字?每个属性的类型?public / private?类里有哪些方法?方法的名字?方法的参数?public / private?类继承有无父类?父类是什么?实现的接口有哪些?(类对象,其实就是对象的说明书 / 蓝本)

四 . 类加载的具体步骤 

类加载总共分为五大环节(现在有的说法是三大环节,这种说法也没有错,只是将中间的三种合并在一起了而已)。

(1)加载 

这就是类加载的第一步,先要把 .class 文件找到:

在代码中先见到类的名字,然后进一步找到对应的 .class 文件(涉及到一系列目录查找的过程),找到文件之后,我们需要打开并读取文件内容。

(2)验证 

验证我们读到的 .class 文件是否正确?是否合法?(在 Java 标准文档中,明确定义了 .class 文件的格式是怎样的) 

验证也就是把第一步 “ 加载 ” 获取到的信息,往上图这个表格里套,检验一下我们读取到的内容能不能合适的套用到这里面去,解析工作能不能正确展开,中间是否会出错。

(3)准备 

准备其实就是分配内存空间,因为我们最终是需要得到一个类对象,而这个类对象是需要有内存空间来去进行存放的。

准备阶段的具体工作就是:根据刚才读取到的内容,确定出类对象需要的内存空间,申请这样的内存空间,并且把内存空间中的所有内容初始化为 0 。

在 Java 中,当我们申请创建一块内存空间,往往都会默认将这块空间设置为 0 ,后续再进行进一步的初始化;而 C / C++ 中却不是这样的,C / C++ 中申请到的内存,不会进行置为 0 的操作,此时,内存上对应的数据就是这块内存上次使用过后残留的数据。

我个人认为 Java 的这种默认置 0 操作很好,可以避免上次残留的数据被当前程序误使用,从而引起一些不必要的 BUG。C / C++ 中不这样做,主要是因为上述的这种初始化全置 0 的操作,可能非常影响性能,特别是对于大块的内存空间而言。

(4)解析 

解析主要是针对类中字符串常量进行处理。解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。

符号引用:我们的字符串常量已经包含在文件里面了。

直接引用:在我们平时谈到的代码中的引用,里面保存了变量的地址。

当我们将这一文件进行类加载,加载到内存中的时候,这个 “ hello ” 就在内存中了,此时 “ hello ” 就有了自己的内存地址,所以我们就把之前的这个偏移量就称之为 “ 符号引用 ” ,类加载之后真实的内存地址就相当于我们的直接引用。

(5)初始化 

初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序,针对类对象做最终的初始化操作。

执行类中的静态代码块,例如静态成员的赋值语句,而且,针对父类也需要进行加载。

五 . 双亲委派模型 

双亲委派模型,这个名字听起来非常的专业,感觉是什么很深奥的知识点昂,其实不然,这只是一个非常简单朴素的过程,是我们类加载的五大环节中第一环节中的一个步骤,其作用就是给定类全限定名,找到对应的 class 文件位置。

(1)类加载器 

在 JVM 中,已经内置了一些类加载器,来完成我们上述 “ 类加载 ” 的过程,类加载器我们可以认为是 JVM 中的功能模块。JVM 默认有三个类加载器:

(2)双亲委派模型的具体工作流程:

上述流程就是我们的 “ 双亲委派模型 ” ,其核心目的就是防止用户自己写的类把标准库的类给覆盖掉,保证标准库的类,被加载的优先级是最高的,扩展库其次,第三方库优先级最低。

这样设定的好处就是,一个程序员,难以知道标准库都有哪些类,当他一不小心创建了一个类,恰好和标准库中的类重命,此时,按照双亲委派模型的原则,就可以保证用户自己写的类不会生效,不会造成不必要的负面破坏。

OKK,今天就先说到这里吧,JVM 的类加载过程以及双亲委派模型是面试的重点考察知识,大家要熟练牢记,咱们下期再见,与诸君共勉!!!


网站公告

今日签到

点亮在社区的每一天
去签到