作为一个程序员,面试的时候都绕不过JVM
,可以说这是我们永远的痛。是不是感觉每次面试的时候,都要重新背一下,不然面试会被虐惨。
那么问题就来了,为什么每次面试都要背?
为什么背完过了断时间就忘了?
除了记忆不深刻,还有一个重要原因是没理解,不理解的东西当然记不久。
所以从这篇开始,我们要开始学习JVM,争取做到理解记忆,将知识点串起来。虽然这块知识很无聊,很枯燥,但是我争取写的简单,有趣点,我们一起加油,好吗?
一、类加载机制
1.1完整流程(简单版)
从上图中我们可以看到,一个简单的Java程序执行流程如下:
1.我们本地编写Java代码
2.编译器帮我们自动编译成.class文件(也可以通过javac命令手动编译,因为这边idea帮我们做了,实际上底层还是调用javac命令)
3.接着,部署到web容器中运行(也可以通过java -jar命令来运行)
这个过程感觉很简单,其实就是将Java的源文件转化成Java认识的.class文件,然后打包运行。
1.2完整流程(复杂版)
其实类从第二步(编译成.class文件)到第三步(运行),这个整个生命周期并不是一两句能说清的。其包括复杂且完整的流程:(是不是被骗了,一看下面的图,好复杂,头晕
🤣)
加载(Loading)
阶段很简单,当程序执行到需要的类时,JVM就会通过类加载器
将其加载到内存中。接下来,我们先看下什么是类加载器
,然后详细讲解整个类加载流程。(稍后再来)
2 类加载器
Java虚拟机设计团队将加载这个动作放在Java虚拟机外面去实现,以便程序能够自己决定何时去获取所需的类。完成这个动作的代码被称为“类加载器”。
以上简单来说,完成加载这个过程的代码叫做类加载器,其有应用程序自己决定。
类加载器
分为3类:
Bootstrap ClassLoader (启动类加载器)
主要负责加载 JDK 安装目录下的核心类库(比如/lib目录下的类),这些核心类库是JVM运行时自身需要用到的。Extension ClassLoader(扩展类加载器)
主要负责加载 JDK 安装目录下的扩展类库(比如/lib/ext目录下的类)。Application ClassLoader(应用类加载器)
负责加载用户自己开发的Java类。
上面的三种类加载器是为了给不同的类加载用的,也就是一个类只能有且只有一个
类加载器对其进行加载。
3双亲委派机制
那么问题就来了,如何保证只有一个类加载器加载呢?
我们先看下他们的之间的关系:(首先启动类加载器是爷爷,扩展类加载器是爸爸,应用程序类加载器是儿子),除了启动类加载器没有父级,其他的类加载器都有父级。
官方说法
:
如果一个类加载器收到
类加载请求
,他首先不会自己
去尝试加载
这个类,而是把这个请求委派
给父类
加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载器请求都应该传送到最顶层的启动类加载器
,只有当父级
的类加载器反馈自己无法完成
这加载请求,子类
加载器才会尝试自己去完成加载
。
白话翻译下
:
如果应用程序类加载器(儿子)获取的请求(工资),他不应该自己先花掉,而是先交给他的父级类加载,也就是扩展类加载器(爸爸),这个时候他也不自己花掉,再交给他的父级类加载器,也就是启动类加载器(爷爷)。如果爷爷说无法加载请求(也就是不花钱),再交给扩展类加载器(爸爸),如果爸爸需要花钱,可以花掉(即加载类),如果爸爸不需要花钱,再交给儿子,让儿子花钱。
优点
1.避免类的重复加载,当父亲已经加载了该类时,子类加载器就没有必要再加载一次。
2.避免Java核心api中的类不会被随意替换,规避风险,防止核心API库被随意篡改。
4.破环双亲委派模型
我们需要知道双亲委派机制并不是一个强制性约束模型,只是Java设计者推荐给我们的类加载器的实现方式。
比如Tomcat就没有实现双亲委派模型,我们来思考一下,为什么他没有实现双亲委派模型?(动动脑袋)
Tomcat是一个web容器,它需要解决版本隔离的问题。即一个web容器可能需要部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
图中可以看出:
CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离。
WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader 实例之间相互隔离。
很显然,tomcat这种加载机制违背了双亲委派机制模型,它为了实现隔离性,每个 webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制。
二、类加载过程
2.1验证阶段
对具体的内容进行校验,确保Class文件的字节流中包含对信息符合Java虚拟机规范的全部要求。其中包括验证文件格式、元数据、字节码、符号引用等各种信息。
2.2 准备阶段
正式为类中定义的变量分配内存并设置类变量初始值。
2.3解析阶段
实际上是把类的符号引用替换为直接引用的过程。
2.4 初始化阶段
之前说过,JVM会在准备阶段给类的静态字段分配空间和默认值。而在初始化阶段,就会正式执行类的初始化代码,对类进行初始化操作,比如一些从配置中取的信息,并不是直接有默认值,而是通过获取才可以。
2.5 使用阶段
在程序中使用类或对象来执行业务。
2.6 卸载阶段
当确定对象不再需要使用时,JVM需要进行垃圾回收。这里也是重中之重。
结语
该篇主要先讲了JVM的类加载器类型,他们是如何配合使用的,为何要破坏类加载器,再描述了文件的完整加载流程,中间配合多张示例图,清楚明了的说明了过程。
如果觉得写得还行,麻烦给个赞👍,您的认可才是我写作的动力!
如果觉得有说的不对的地方,欢迎评论指出。也可以关注我的公众号学习Java的小姐姐
,我们一起讨论下。
好了,拜拜咯。
参考文献
https://blog.csdn.net/huihuidage/article/details/107425939