共计 3690 个字符,预计需要花费 10 分钟才能阅读完成。
前言
明天咱们来讲讲 jvm
里类加载
的过程,咱们写了那么多类,却不晓得类的加载过程,岂不是很 难堪
。
jvm
的启动是通过疏导类加载器(bootstrap class loader)创立一个初始类(initial class)来实现的,这个类是由 jvm
的具体实现指定的。[来自官网标准]
jvm 组成构造之一就是 类装载器子系统
,咱们明天就来认真讲讲这个组件。
Java 代码执行流程图
大家通过这个流程图,理解一下咱们写好的 Java 代码是如何执行的,其中要经验 类加载器
这个流程,咱们就来认真讲讲这外面的知识点。
类加载子系统
类加载零碎架构图
临时看不懂这两张图没关系,跟着 老哥
往下看
类的生命周期
类的生命周期包含:加载、链接、初始化、应用和卸载,其中 加载
、 链接
、 初始化
,属于 类加载的过程
,咱们上面认真解说。应用是指咱们 new 对象进行应用,卸载指对象被垃圾回收掉了。
类加载的过程
- 第一步: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()
获取并操作这个加载器
- 第四个:自定义加载器
个别状况下,以上 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()
类加载机制—双亲委派机制
jvm 对 class 文件采纳的是按需加载的形式,当须要应用该类时,jvm 才会将它的 class 文件加载到内存中产生 class 对象。
在加载类的时候,是采纳的 双亲委派机制
,即把申请交给父类解决的一种工作委派模式。
- 工作原理
(1)如果一个 类加载器
接管到了 类加载
的申请,它本人不会先去加载,会把这个申请委托给 父类加载器
去执行。
(2)如果父类还存在父类加载器,则持续向上委托,始终委托到 启动类加载器:Bootstrap ClassLoader
(3)如果父类加载器能够实现加载工作,就返回胜利后果,如果父类加载失败,就由子类本人去尝试加载,如果子类加载失败就会抛出 ClassNotFoundException
异样,这就是 双亲委派模式
- 第三方包加载形式:反向委派机制
在 Java 利用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口容许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI 等,这些 SPI 的接口属于 Java 外围库,个别存在 rt.jar 包中,由 Bootstrap 类加载器加载。而 Bootstrap 类加载器无奈间接加载 SPI 的实现类,同时因为双亲委派模式的存在,Bootstrap 类加载器也无奈反向委托 AppClassLoader 加载器 SPI 的实现类。在这种状况下,咱们就须要一种非凡的类加载器来加载第三方的类库,而线程上下文类加载器(双亲委派模型的破坏者)就是很好的抉择。
从图可知 rt.jar 外围包是有 Bootstrap 类加载器加载的,其内蕴含 SPI 外围接口类,因为 SPI 中的类常常须要调用内部实现类的办法,而 jdbc.jar 蕴含内部实现类 (jdbc.jar 存在于 classpath 门路) 无奈通过 Bootstrap 类加载器加载,因而只能委派线程上下文类加载器把 jdbc.jar 中的实现类加载到内存以便 SPI 相干类应用。显然这种线程上下文类加载器的加载形式毁坏了“双亲委派模型”,它在执行过程中摈弃双亲委派加载链模式,使程序能够逆向应用类加载器,当然这也使得 Java 类加载器变得更加灵便。
- 沙箱平安机制
自定义 String 类,然而在加载自定义 String 类的时候会率先应用疏导类加载器加载,而疏导类加载器在加载的过程中会先加载 JDK 自带的文件(rt.jar 包中的 javalangString.class),报错信息说没有 main 办法就是因为加载的 rt.jar 包中的 String 类。这样能够保障对 Java 外围源代码的爱护,这就是沙箱平安机制。
IT 老哥
我是一个通过大学四年自学,进入大厂做高级 java 开
发的程序员。关注我,每天分享技术干货,助你进大
厂。我能够,你也能够的。
如果你感觉文章写的还不错,请帮老哥点个赞!