java 类加载机制简析
类的加载:指的是
JVM
将类的.class
文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class
对象,用来封装类在方法区类的对象,加载的最终目标是堆中的Class
对象Class 对象封装了类在方法区内的数据结构,并且向 Java 程序员提供了访问方法区内的数据结构的接口
JVM 将类加载过程分为三个步骤:
装载(Load)
,链接(Link)
和初始化(Initialize)
类装载(不同加载器的主要不同点)
来源(.class文件来源):
- 从本地系统直接加载
.class
- 网络下载
.class
- 从
zip,jar
等归档文件中加载.class
- 从专有数据库中提取
.class
- 将 Java 源文件动态编译为
.class
文件(服务器)
类链接
验证:确保被加载类的正确性;
准备:为类的静态变量分配内存,并将其初始化为默认值;
解析:把类中的符号引用转换为直接引用;
类初始化
-
WHEN:
- 创建类的实例,也就是 new 一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射
Class.forName()
- 初始化一个类的子类(会首先初始化子类的父类)
- JVM 启动时标明的启动类,即文件名和类名相同的那个类
-
HOW:
如果这个类还没有被装载和链接,那先进行装载和链接
假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句
/**
* 初始化伪代码
*/
init (Class child) {
if (child.hasParent()) {
Class<?> parent = child.getParent();
loadAndLink(parent);
init(parent);
}
/**
* 初始化静态成员代码。。。。
*/
return;
}
类加载器
JVM 的类加载是通过
ClassLoader
及其子类来完成
- WHAT:实际上,各种类加载器其实主要对
.class
源进行操作,并将其转化成字节序列- WHY:在 JVM 中,一个类由其全限定名和一个加载类
ClassLoader
的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间- HOW:组成了如下图所示的 ,以 ”双亲委托机制“为”桥梁“的
ClassLoader Architecture
注意:
BootstrapClassLoader:
由 c++ 编写而成,不是ClassLoader
的子类
ExtClassLoader
与AppClassLoader
没有类继承关系,却通过 parent 属性使其构成父子类加载器的关系
上图表示出 java 类加载机制中的 “双亲委托机制“
双亲委托机制
-
WHAT:当前类加载器在调用
loadClass()
加载类时,主要进行下步骤:- 检查类是否已被加载过,调用
findClassLoaded()
查看当前类加载器是否存在 class 的缓存 - 若类未被加载过,递归委托父类加载器调用
loadClass()
加载类,若无,则findBootstrapClassOrNull()
完成类加载 - 若以上步骤都不能完成类加载,则调用
findClass()
尝试当前类加载器完成加载,若加载成功则缓存
特点:
- 从子到父共享缓存
- 从父到子尝试自加载
- 检查类是否已被加载过,调用
WHY:双亲委托机制主要是为了 ”类加载器之间共享缓存“ 和 "类加载器的优先级问题",保证基础类的类加载器统一的问题
-
HOW:具体代码实现:
/** * //:ClassLoader 类中的 loadClass() 源码 * loadClass 是加载类的入口,也是双亲委托机制的实现 * @param name 必须为二进制名,即类的全限定名 * @param resolve 是否链接类,链接绑定类与加载器 * @return 类加载完成后返回存储在堆中 Class * 方法 loadClass 并未被 final 修饰,代表可以被复写,从而破坏“双亲委托” */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 检查类是否已被加载过 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //递归委托父类加载器加载类 c = parent.loadClass(name, false); } else { //无父类加载器,则调用启动类加载器加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { long t1 = System.nanoTime(); //递归委托后仍然无法完成类加载,则使用当前类加载器加载 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
自定义类加载器(extends ClassLoader)的方式:
- override
loadClass()
破化双亲委托机制(不推荐)- override
findClass()
兼容双亲委托机制,通过复写提供的findClass()
来实现在双亲委托机制的基础上来自定义类加载器
- 先内后外,加载类时,遵循先双亲委托,后自定义加载
ClassLoader
的三个构造器:
private final ClassLoader parent; //父类加载器,只在构造器中一次性指定,不提供public方法设置
/**
* ClassLoader 无参构造器
* 内部调用带参构造器,使用getSystemClassLoader()作为parent形参的实参
* getSystemClassLoader()返回的是sun.misc.Launcher$AppClassLoader
*/
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
/**
* 带参构造器
* @parent 用来指定父类加载器
*/
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
/**
* 带参构造器
*/
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent; //设置父类加载器
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
实现自定类加载器:
/**
* //: findClass 方法源码
* @name
* 该方法只抛出一个 ClassNotFountException(name)
* 表明此方法被设计来复写,作为自定义加载器类的接口
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
// 自定义时,在此处添加代码,将 .class 源解析为 byte[] classData
// return defineClass(name, classData, 0, classData.length);
}
/**
* //: defindClass 方法源码
* final 修饰,不能被子类复写,提供真正意义上 JVM 对类进行加载的接口
* 将由.class 解析成的字节序列,存储于方法区,然后在堆中生成对应的Class对象
*/
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
___$$$$$$$_____________________________
________________________$$$$$$$$$$___________________________
________________________$$$$$$$$$$$__________________________
_________________________$$$$$$$$$$$$$$______________________
__________________________$$$$$$$$$$$________________________
_____________________________$$$$$$$$$$$$$___________________
___________________________$$$$$$$$$$________________________
_________________________$$$$$$$$$$$$$$$_____________________
________________$$$______$$$$$$$$$$$$$$______________________
______________$$$$$$$$_____$$$$$$__$$$$$_____________________
_____________$$$$$$$$$$_____$$$$____$$$$$____________________
___________$$$$$$_$$$$$$$$__$$$$______$$$$___________________
__________$$$$$_____$$$$$$$$_$$$$_______$$$__________________
________$$$$$_________$$$$$$$$$$$$_______$$$_________________
_______$$$_____________$$$$$$$$$$$________$$$________________
_____$$$________________$$$$$$$$$$________$$$$$$_____________
__$$$$$$__________________$$$$$$$
-------------------------------------------------------------