浅谈Java中的类加载机制与双亲委派

98 阅读6分钟

这是自己的第二篇笔记,本人的笔记主要用于记录自己的学习轨迹。
最近面试的时候经常被问到,在Java中,一个类是怎么被加载的?因此也想自己总结一下。内容深度可能不深,但也希望能够帮助到大家。

对于Java的类加载学习之前,先要了解一下,Java中的一个类,是如何开始工作的?

Java代码是如何运行的?

通常情况下,不管是我们使用javac命令还是IDE工具的build Project,其实都是进行代码的编译操作,此时,Java代码会经过编译生成一个个.class字节码文件,这个过程可以叫做编译期。如果你使用的是IDE工具,这个过程通常不需要我们主动进行,工具会在代码运行时帮助我们自动完成。

以下是一个编译的例子

这是自己写的一个实体类,包含内部方法

原java文件.png

经过编译后,是什么样子呢?

编译过后的Java文件.png

可以看到,只是将无参构造方法显示表示了出来。

这里是因为使用的是IDEA开发工具完成的编译,IDEA内部则是使用名为Fernflower反编译工具,将编译生成的字节码文件(也就是我们所常说的.class文件)反编译为我们可以看懂的样子(java源代码)。 但是实际上,真实的字节码文件内部还包含了多种信息,如:JMM执行指令、类的元数据信息(类名、属性、方法等)等一系列信息,下面给大家展示.class文件的构成,在未来的文章中也会针对字节码文件继续进行更新,本次主要讲类加载!

class文件内部存储结构.png

编译完成的字节码对象就可以在JRE的环境下直接运行啦!

接下来就是运行期: 当我们拥有了.class文件后,就需要JVM来运行我们的字节码文件,也就是我们常说的代码运行(run)阶段。

在Run阶段中,JVM会首先通过类加载器去加载当前的.class文件,然后将字节码文件放入运行时数据区,再通过执行引擎将字节码文件中的编码转换为计算机可识别运行的机器码,最终由操作系统执行。

以上基本就是我们在运行一段Java代码时所经历里的步骤,可以总结为:程序员书写代码逻辑-->IDEA-->.java文件-->编译-->.class文件-->加载解析-->JVM-->执行引擎转化为机器码-->内存-->CPU分配运行

大概讲完了如何运行,那么类到底是怎么加载的?

Java中的一个类是如何被加载的?

在一个类从编译加载到虚拟机内存到从虚拟机内存中卸载一共会经历以下7个过程:

  1. 加载:在加载过程中,Java中的类会通过类加载器加载到JVM虚拟机的过程,在这个过程中会经历3个阶段(就是做3件事),分别是:

1、通过类的全限定名来获取当前类的二进制流
2、将字节流的静态存储结构转化为方法区内的运行时数据结构(在类中以static修饰的变量等,static final修饰的叫做常量,常量在类加载阶段会直接赋值,而不会先赋初始值后再赋实际的值)
3、在内存中生成代表这个类的java.lang.Class类对象,作为方法区可访问当前类的数据访问入口。

  1. 验证
    验证阶段主要作用在于:JVM虚拟机在加载一个类的时候,需要确认这个类是一个可被加载的类(符合规范的类),也就是确保所生成的class字节流中所包含的信息符合《Java虚拟机规范》,验证阶段具体可分为

1、文件格式验证过程:文件格式验证过程主要是为了查看字节流是否符合一个字节码文件(class)规范,并且允许被当前版本的JVM所处理。
2、元数据验证过程:主要针对字节码文件所描述的信息进行语义检查,保证当前加载类符合Java的语言规范,比如:是否继承了不能被继承的类,抽象类是否具备最少一个抽象方法等。
3、字节码验证过程:主要针对字节码的code进行校验,保证被校验数据不会再运行时对虚拟机出现伤害行为(不会出现数据结构/数据类型不匹配的问题导致的程序中断等,保证类的多态是正确有效的,也就是父类可接受子类,但是子类不可以接收父类)。
4、符号验证过程:查看当前类是否缺少或被禁止访问其所依赖的一些外部类、方法、字段等共享资源。

  1. 准备

准备阶段的主要工作是为类中的变量以及静态变量分配内存空间并赋初始值,在该阶段中,变量的内存需要在方法区中分配,但方法区本身是逻辑分区(这部分后续会补全,知识盲区),注意:在该阶段并不分配具体的值,而是赋初始值,如:int赋0,引用类型赋null这种。
4. 解析

解析阶段时将符号引用替换为直接引用的过程,此时的变量指针会直接指向方法区/常量池中的具体地址/值。

  1. 初始化 初始化是类加载的最后一个步骤,这部分后续会单发一篇文章进行整理。

什么叫做双亲委派?

在Java类加载中,通常会提到类加载的双亲委派机制,那么什么叫做双亲委派呢? 简单来说就是自己不做,找父亲做,父亲也不做,找父亲的父亲做!
是不是看起来类加载机制比较懒,明明可以自己加载,为什么不这么做呢?
在Java中,双亲委派机制有效地保证了Java中内置类的唯一性,如果没有双亲委派机制,那我们自己写一个Object类,是不是内置的Object就无法使用了?JVM通过双亲委派机制有效地解决了这个方法,而且在Java中,一个类如果被加载了,便不会重复加载重名的第二个类。
那么如何打破双亲委派机制呢?
在tomcat中,针对一个模块,我们可以完成一次类的加载,而在tomcat中由于每个模块是分割的,所以此时我们在每个模块中都写了同样的类,在加载时也会同时加载两个一模一样的类。打破了类加载的唯一性。

今天的类加载与双亲委派就先唠到这吧,入门小白,希望有说的不对的地方,大佬多多指正,真的很想进步诶!!!

参考文档:
# Jvm学习笔记(四) 类加载原理

类加载机制