哈啰,各位小伙伴们,这里是每天进步一点点的花栗鼠小K
关于常用设计模式的介绍已经接近尾声了,小K这段时间来,一直在思索接下来帮助大家理解什么样的知识。上上期小K带领大家了解了Java中的基础:反射机制,其实Java中称得上基础的知识,一抓一大把,比如垃圾回收机制、JVM
内存模型等,介于小K和其他小伙伴的分工,这期呢,小K打算介绍面试中常问到的另一个基础知识:类加载。
写了那么多类,却不知道类的加载过程,岂不是很社死嘛
01
—
类加载
我们先通过一个流程图看一下Java代码的执行过程
上图是Java代码从编译、解析到执行的全部过程,其中类加载器部分的流程,就是本期要介绍的重点
类加载的整个过程,其实就是在 JVM
中进行的,栗子为有篇关于 JVM
内存模型的文章,有兴趣的小伙伴可以看看——>JMM其实也没那么复杂(话说,怎么总是我给他打广告)
JVM
类加载系统可以概括为5部分,如图
小K接下来将按照顺序依次带领大家了解
类加载系统结构
类加载系统的整体结构如下
Java文件经过编译生成字节码后,会经过类加载器的处理。进入类加载器之后,大致要经历三个阶段:加载阶段、链接阶段、初始化阶段
加载阶段涉及到了不同的类加载器以及双亲委派机制
链接阶段则是将加载阶段创建好的 class
类合并到Java虚拟机中,使其能够执行
初始化阶段就是执行类的构造器方法 init()
的过程
类的生命周期和加载过程
一个类从被加载到虚拟机内存中开始,到卸载出内存为止。它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、准备、解析统称为链接。
类加载过程贯穿类生命周期的始终,二者基本一致,如图
-
第一步:
Loading
加载 通过类的全限定名(包名 + 类名),获取到该类的.class
文件的二进制字节流 将二进制字节流所代表的静态存储结构,转化为方法区运行时的数据结构在内存中生成一个代表该类的
java.lang.Class
对象,作为方法区这个类的各种数据的访问入口总结:加载二进制数据到内存 —> 映射成
JVM
能识别的结构 —> 在内存中生成class
文件。 -
第二步:
Linking
链接 链接是指将上面创建好的class
类合并至Java虚拟机中,使之能够执行的过程,可分为验证、准备、解析三个阶段。① 验证(
Verify
)确保class文件中的字节流包含的信息,符合当前虚拟机的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全。② 准备(
Prepare
)为类中的静态字段分配内存,并设置默认的初始值,比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了 ③ 解析(
Resolve
)解析阶段的目的,是将常量池内的符号引用转换为直接引用的过程(将常量池内的符号引用解析成为实际引用)。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。) 事实上,解析器操作往往会伴随着
JVM
在执行完初始化之后再执行。符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《Java 虚拟机规范》的Class
文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。解析动作主要针对类、接口、字段、类方法、接口方法、方法类型等。对应常量池中的
CONSTANT_Class_info
、CONSTANT_Fieldref_info
、CONSTANT_Methodref_info
等。 -
第三步:
Initialization
初始化 初始化就是执行类的构造器方法init()
的过程。这个方法不需要定义,是javac
编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并来的。若该类具有父类,会
JVM
保证父类的init
先执行,然后在执行子类的init
。
类加载器
类加载器的分类和执行次序如图所示
-
第一个:启动类/引导类:
Bootstrap ClassLoader
这个类加载器使用 C/C++ 语言实现的,嵌套在JVM
内部,Java 程序无法直接操作这个类。它用来加载Java核心类库,如:JAVA_HOME/jre/lib/rt.jar
、resources.jar
、sun.boot.class.path
路径下的包,用于提供JVM
运行所需的包。并不是继承自java.lang.ClassLoader
,它没有父类加载器它加载扩展类加载器和应用程序类加载器,并成为他们的父类加载器。出于安全考虑,启动类只加载包名为:java
、javax
、sun
开头的类 -
第二个:扩展类加载器:
Extension ClassLoader
Java语言编写,由sun.misc.Launcher$ExtClassLoader
实现,我们可以用Java程序操作这个加载器派生继承自java.lang.ClassLoader
,父类加载器为启动类加载器。从系统属性:java.ext.dirs
目录中加载类库,或者从JDK
安装目录:jre/lib/ext
目录下加载类库。我们就可以将我们自己的包放在以上目录下,就会自动加载进来了。 -
第三个:应用程序类加载器:
Application Classloader
Java语言编写,由sun.misc.Launcher$AppClassLoader
实现。派生继承自java.lang.ClassLoader
,父类加载器为启动类加载器。它负责加载环境变量classpath
或者系统属性java.class.path
指定路径下的类库。它是程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。我们可以通过ClassLoader#getSystemClassLoader()
获取并操作这个加载器 -
第四个:自定义加载器:
User ClassLoader
一般情况下,以上 3 种加载器能满足我们日常的开发工作,不满足时,我们还可以自定义加载器。比如用网络加载 Java 类,为了保证传输中的安全性,采用了加密操作,那么以上3种加载器就无法加载这个类,这时候就需要自定义加载器 -
自定义加载器实现步骤
继承
java.lang.ClassLoader
类,重写findClass()
方法 如果没有太复杂的需求,可以直接继承URLClassLoader
类,重写loadClass()
方法,具体可参考AppClassLoader
和ExtClassLoader
。获取
ClassLoader
几种方式它是一个抽象类,其后所有的类加载器继承自
ClassLoader
(不包括启动类加载器)// 方式一:获取当前类的 ClassLoader
clazz.getClassLoader()
// 方式二:获取当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader()
// 方式三:获取系统的 ClassLoader
ClassLoader.getSystemClassLoader()
// 方式四:获取调用者的 ClassLoader
DriverManager.getCallerClassLoader()
02
—
双亲委派机制
JVM
对 class
文件采用的是按需加载的方式,当需要使用该类时,JVM
才会将它的 class
文件加载到内存中产生 class
对象。
在加载类的时候,是采用的双亲委派机制
,即把请求交给父类处理的一种任务委派模式。
双亲委派机制的流程如下:
如果一个 类加载器
接收到了类加载
的请求,它自己不会先去加载,会把这个请求委托给父类加载器
去执行。如果父类还存在父类加载器,则继续向上委托,一直委托到 启动类加载器:Bootstrap ClassLoader
如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出 ClassNotFoundException
异常,这就是双亲委派模式
03
—
总结
本期介绍了Java的一个基础知识:类加载。主要是帮助大家理解类从创立到执行的过程,以及 JVM
在加载类的过程中是怎样执行的,其主要核心还是类的加载过程和双亲委派机制。
本期就到这了,这里是花栗鼠小K,下次有🌰,我再来,拜拜~~~
关注六只栗子,面试不迷路!
作者 花栗鼠小K
编辑 一口栗子
原文始发于微信公众号(六只栗子):闲话Java类加载
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88494.html