0x01.类加载器概述
1.什么是类加载器?
- 类加载器负责动态加载Java类到Java虚拟机的内存空间中。
- 类加载器子系统负责从文件系统或者网络中加载Class文件。(Class文件在文件开头有特定的文件标识)
- 加载的类信息存放于方法区的内存空间中。
- 类加载器只负责class文件的加载,至于它是否可运行,则由Execution Engine决定。
- 通常是按需加载,即第一次使用该类时才加载。
2.类加载器模型图:
0x02.类加载的执行流程
-
类加载的执行流程分为三步:
- Loading(加载)
- Linking(链接)
- Initialization(初始化)
1.Loading的过程:
- 1.通过类的全限定名获取定义此类的二进制字节流。
- 2.将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构。
- 3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口。
2.Linking的过程:
-
Linking又分为三个阶段:
- Verify(验证)
- Prepare(准备)
- Resolve(解析)
Verify:
- 验证以确保Class文件的字节流所包含的信息符合当前虚拟机的要求,保证被加载的类的正确性。
- 主要的验证方式:元数据验证,字节码验证,符号引用验证,文件格式验证。
Prepare:
-
为类变量分配内存,并且设置该类变量的默认初始值。
-
细节:
- Prepare阶段不会为实例变量分配初始化。【原因:类变量分配在方法区中,而实例变量随着对象一起分配到堆中】
- Prepare阶段不包含final修饰的static。【原因:final在编译的时候就已经分配了,该阶段会显式的初始化】
Resolve:
-
将常量池内的符号引用转换为直接引用的过程。
- 符号引用是一组符号来描述所引用的目标。
- 直接引用是直接指向目标的制作,相对偏移量或一个间接定位到目标的句柄。
- 解析的动作针对类,接口,字段,类方法,接口方法,方法类型等。
-
解析操作往往会在JVM执行完初始化之后在执行。
3. Initialization的过程:
-
初始化的过程实际上就是执行类构造器方法
<clinit>()
的过程。<clinit>()
不是类的构造器,而是虚拟机视角下的<init>()
。- 若一个类有父类,JVM会保证子类的
<clinit>()
执行前,父类的<clinit>()
已经执行完毕。
-
此方法无需定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来的。
-
构造器方法中指令按语句在源文件中出现顺序执行。
-
虚拟机必须保证一个类的
<clinit>()
方法在多线程下被同步加锁。
0x03.类加载器
- JVM支持两种类型的加载器,分别为引导类加载器和自定义加载器。
- JVM将所有派生于抽象类
ClassLoader
的类加载器划分为自定义类加载器。
加载器的分类图:
- 【注】:图中所示是包含关系。
1.启动类加载器(Bootstrap):
- 也叫引导类加载器,(Bootstrap ClassLoader)
- 该类使用C/C++语言实现,嵌套在JVM的内部。【我们不能获取到】
- 该类用于加载Java的核心类库,用于提供JVM自身需要的类。
- 该类还用于加载扩展类和应用程序类加载器,并指定为它们的父加载器。
- 该类没有父加载器。且只加载包名为
java
,javax
,sun
等开头的类。(出于安全考虑)
2.扩展类加载器(Extension):
- 是由java编写的,派生于
ClassLoader
类。(sun.misc.Launcher$ExtClassLoader
实现) - 父类加载器为启动类加载器。
- 用于从
java.ext.dirs
系统属性所指定的目录中加载类库。(或从JDK的安装目录的jre/lib/ext
子目录下加载类库)(如果用户自己创建的jar文件在此目录,也由扩展类加载器加载)
3.应用程序类加载器(AppClassLoader)
-
是由java编写的,派生于
ClassLoader
类。(sun.misc.Launcher$ExtClassLoader
实现) -
父类加载器为扩展类加载器。
-
用于加载环境变量classpath或系统属性。
-
该类加载器是程序中默认的类加载器,一般的java应用的类都由该类加载器完成加载。
-
获取该类加载器的方法:
- ClassLoader类的
getSystemClassLoader
方法。
- ClassLoader类的
4.用户自定义加载器:
使用自定义加载器的原因:
- 防止源码泄露。
- 修改类加载的方式。
- 扩展加载源。
- 修改加载类。
自定义加载器的实现步骤:
- (1)继承抽象类
java.lang.ClassLOader
,重写一些方法,实现自己的类加载器。 - (2)最好把自定义加载器的逻辑写到
findCLass()
方法中。 - (3)如果没有过于复杂的需求,可以直接继承
URLClassLoader
类。(能够避免自己编写findClass()
方法和其获取字节流的方式)
0x04.ClassLoader
ClassLoader
是一个抽象类,除启动类加载器外,其它类加载器都继承自ClassLoader。
1.常用方法:
public final ClassLoader getParent()
:返回该类的超类加载器。(若超类是引导类,则返回null)public Class<?> loadClass(String name) throws ClassNotFoundException
:加载名称为name
的类,返回结果为java.lang.Class
类的实例。protected Class<?> findClass(String name) throws ClassNotFoundException
:查找名称为name
的类,返回结果为java.lang.Class
类的实例。- -
protected final Class<?> findLoadedClass(String name)
:查找名称为name
的已经被加载过的类,返回结果为java.lang.Class
类的实例。
2.获取ClassLoader的途径:
1.获取当前类的ClassLoader:
clazz.getClassLoader()
(clazz
为具体的类)
2.获取当前线程上下文的ClassLoader:
Thread.currentThred().getContextClassLoader()
3.获取系统的ClassLoader:
ClassLoader.getSystemClassLoader()
4.获取调用者的ClassLoader:
DriverManager.getCallerClassLoader()
0x05.双亲委派机制
- Java虚拟机对class文件采用的是按需加载的方式,需要使用一个类的时候才会将它的class文件加载到内存中生成class对象。
- 加载某个类的class文件时,java虚拟机采用的是双亲委派机制【把请求交由父类处理,是一种任务委派模式】
1.双亲委派机制工作原理:
- (1)如果一个类加载器收到了类加载的请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
- (2)如果父类加载器还存在父类加载器,则继续向上委托,请求最终会到达顶层的启动类加载器。
- (3)如果父类加载器可以完成类加载,就成功的返回,如果无法完成加载,子加载器才会尝试自己去加载。
2.双亲委派机制原理图:
3.双亲委派机制的优势:
-
避免类的重复加载。
-
保护程序的安全,防止核心API被篡改。【沙箱安全机制】
-
细节:JVM中表示两个class对象是否为同一个类的两个必要条件:
- 类的完整类名必须完全一致。(包括包名)
- 加载这个类的ClassLoader必须相同。(ClassLoader示例对象)
本文章是《JVM从零到一系列》的一篇,这个系列将会持续更新。
此系列文章所有内容来自于作者的整理,主要参考以下方面:
- 《深入理解Java虚拟机》
- 宋红康老师系列课程
- 大厂面试题
- JVM8官方参考文档
- JVM8源代码
本文所有图均为自己绘制,转载请附上作者署名和本文链接,谢谢啦^^~~
JVM相关的资料确实不是很多,希望我的总结能给大家一些帮助!
谢谢您的观看!
作者署名 – ATFWUS