目录
1. 类加载过程
2. 类加载器
一、类加载过程
1. 类加载过程:
分为三步:加载->连接->初始化
其中连接过程分为: 验证->准备->解析
图示
1.1 什么时候开始类的初始化?
(1)最常见的Java代码场景:
1.使用new关键字实例化对象的时候
2.读取或设置一个类的静态字段的时候(被final修饰、已在编译器把结果放入常量池的静态字段除外)
3.调用一个类的静态方法的时候。
(2)使用java.lang.reflect包的方法对类进行反射调用的时候,
(3)初始化一个类的时候,如果其父类没有进行过初始化,则先对其父类初始化
(4)虚拟机启动时候,指定一个要执行的主类(包含main()方法的那个类)
(5)如果一个Java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_outStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
1.2 "加载"阶段
"加载"是"类加载"过程的一个阶段,注意不要混淆,在加载阶段,需要完成以下3件事情
(1)通过一个类的全限定名来获取定义此类的二进制字节流
(2)将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
(3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
加载阶段完成后,二进制字节流就按照虚拟机所需的格式存储在方法区之中
1.3 准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。这些变量所使用的内存都将在方法区中进行分配
注意:这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量会在对象实例化随着对象一起分配在Java堆中。这里的初始值"通常情况"下是数据类型的零值。如果类字段的字段属性表中存在ConstantValue属性,那么在准备阶段变量value就会被初始化为ConstantValue属性所指定的值
// value在准备阶段过后的初始值为0而不是123
public static int value = 123;
// 编译时Javac将会为value生成ConstantValue属性
// 在准备阶段虚拟机会根据ConstantValue的设置将value赋值为123
public static final int value1 = 123;
二、类加载器
1.定义
类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为""类加载器“
每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个ClassLoader加载。ClassLoader就像一个容器,里面装了很多已经加载的 Class 对象。
2.双亲委派模型
2.1 三种系统提供的类加载器
- 启动类加载器(Bootstrap ClassLoader),负责加载<JAVA_HOME>\lib目录,如rt.jar
- 扩展类加载器(Extension ClassLoader),负责加载<JAVA_HOME>\lib\ext目录
- 应用程序类加载器(Application ClassLoader),负责加载用户类路径(ClassPath)上所指定类库
2.2 双亲委派工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类。而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。(类加载器之间的父子关系一般不会以继承的关系来实现,而是都使用组合关系来复用父加载器的代码)
2.3 工作过程详情
Application ClassLoader 在加载一个未知的类名时,它并不是立即去搜寻 Classpath,它会首先将这个类名称交给 Extension ClassLoader来加载,如果ExtensionClassLoader可以加载,那么 Application ClassLoader 就不用加载了。否则它就会搜索 Classpath 。
而Extension ClassLoader 在加载一个未知的类名时,它也并不是立即搜寻 ext 路径,它会首先将类名称交给 Bootstrap ClassLoader来加载,如果 Bootstrap ClassLoader 可以加载,那么 Extension ClassLoader 也就不用加载了。否则它就会搜索 ext 路径下的 jar 包。
这三个 ClassLoader 之间形成了级联的父子关系,每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器。
2.4 双亲委派模型的好处
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了Java的核心API不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类(存放在rt.jar中)的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。
2.5 如何避免双亲委派机制
自己定义一个类加载器,重写 loadClass() 。
Mark
- 这块的知识点有点难啃,以后会回来改正的和补充的