image.png

作为一个程序员,面试的时候都绕不过JVM,可以说这是我们永远的痛。是不是感觉每次面试的时候,都要重新背一下,不然面试会被虐惨。

那么问题就来了,为什么每次面试都要背?``为什么背完过了断时间就忘了?除了记忆不深刻,还有一个重要原因是没理解,不理解的东西当然记不久。

所以从这篇开始,我们要开始学习JVM,争取做到理解记忆,将知识点串起来。虽然这块知识很无聊,很枯燥,但是我争取写的简单,有趣点,我们一起加油,好吗?

一、类加载机制

1.1完整流程(简单版)

image.png

从上图中我们可以看到,一个简单的Java程序执行流程如下:

1.我们本地编写Java代码

2.编译器帮我们自动编译成.class文件(也可以通过javac命令手动编译,因为这边idea帮我们做了,实际上底层还是调用javac命令)

3.接着,部署到web容器中运行(也可以通过java -jar命令来运行)

这个过程感觉很简单,其实就是将Java的源文件转化成Java认识的.class文件,然后打包运行。

1.2完整流程(复杂版)

其实类从第二步(编译成.class文件)到第三步(运行),这个整个生命周期并不是一两句能说清的。其包括复杂且完整的流程:(是不是被骗了,一看下面的图,好复杂,头晕🤣)

image.png

加载(Loading)阶段很简单,当程序执行到需要的类时,JVM就会通过类加载器 将其加载到内存中。接下来,我们先看下什么是类加载器,然后详细讲解整个类加载流程。(稍后再来)

2 类加载器

Java虚拟机设计团队将加载这个动作放在Java虚拟机外面去实现,以便程序能够自己决定何时去获取所需的类。完成这个动作的代码被称为“类加载器”。

以上简单来说,完成加载这个过程的代码叫做类加载器,其有应用程序自己决定。

image.png

类加载器分为3类:

  1. Bootstrap ClassLoader (启动类加载器)主要负责加载 JDK 安装目录下的核心类库(比如/lib目录下的类),这些核心类库是JVM运行时自身需要用到的。
  2. Extension ClassLoader(扩展类加载器)主要负责加载 JDK 安装目录下的扩展类库(比如/lib/ext目录下的类)。
  3. Application ClassLoader(应用类加载器)负责加载用户自己开发的Java类。

上面的三种类加载器是为了给不同的类加载用的,也就是一个类只能有且只有一个类加载器对其进行加载。

3双亲委派机制

那么问题就来了,如何保证只有一个类加载器加载呢?

我们先看下他们的之间的关系:(首先启动类加载器是爷爷,扩展类加载器是爸爸,应用程序类加载器是儿子),除了启动类加载器没有父级,其他的类加载器都有父级。

image.png

官方说法

如果一个类加载器收到类加载请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载器请求都应该传送到最顶层的启动类加载器,只有当父级的类加载器反馈自己无法完成这加载请求,子类加载器才会尝试自己去完成加载

白话翻译下

如果应用程序类加载器(儿子)获取的请求(工资),他不应该自己先花掉,而是先交给他的父级类加载,也就是扩展类加载器(爸爸),这个时候他也不自己花掉,再交给他的父级类加载器,也就是启动类加载器(爷爷)。如果爷爷说无法加载请求(也就是不花钱),再交给扩展类加载器(爸爸),如果爸爸需要花钱,可以花掉(即加载类),如果爸爸不需要花钱,再交给儿子,让儿子花钱。

优点

1.避免类的重复加载,当父亲已经加载了该类时,子类加载器就没有必要再加载一次。

2.避免Java核心api中的类不会被随意替换,规避风险,防止核心API库被随意篡改。

4.破环双亲委派模型

我们需要知道双亲委派机制并不是一个强制性约束模型,只是Java设计者推荐给我们的类加载器的实现方式。

比如Tomcat就没有实现双亲委派模型,我们来思考一下,为什么他没有实现双亲委派模型?(动动脑袋)

image.png

Tomcat是一个web容器,它需要解决版本隔离的问题。即一个web容器可能需要部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。

image.png

图中可以看出:
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的小姐姐,我们一起讨论下。

好了,拜拜咯。

参考文献

blog.csdn.net/huihuidage/…

作者:学习Java的小姐姐
链接:https://juejin.cn/post/6892675934806573064