类加载
1、类加载阶段
1) 加载
- 将类的字节码载入方法区(元空间jdk1.8),内部采用C++
- 有父类先加载父类
- 反射是通过对象头class地址找到元空间中Class类对象,从中获取_fields, _methods等信息!
2) 连接
1、验证:字节码是否符合规范
2、准备:给类static变量分配空间,确认默认值==(static int a = 10)==
- static 变量确认默认值和赋值是两个步骤,确认默认值在准备阶段完成a值为0,赋值在初始化阶段完成a值为10
- 但如果 static 变量 是 final 的基本类型,以及字符串常量,那么赋值在准备阶段完成
- 如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成将常量池中的符号引用解析为直接引用
3、解析:将常量池中的符号引用解析为直接引用(确定引用类的地址)
3) 初始化
初始化即调用 () 方法 ,该方法包含类中所有静态变量的赋值与所有静态代码块执行,虚拟机会保证这个类的『构造方法』的线程安全。
*类初始化是【懒惰的】:
- main 方法所在的类,总会被首先初始化
- 首次访问这个类的静态变量或静态方法时
- 子类初始化,如果父类还没初始化,会引发
- 子类访问父类的静态变量,只会触发父类的初始化
- Class.forName
- new 会导致初始化
不会导致类初始化的情况:
- 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化(连接阶段)
- 类对象.class 不会触发初始化(加载阶段)
- 创建该类的数组不会触发初始化
*利用类加载初始化特性实现懒汉单例模式
public class Singleton {
//私有构造器,不能被外部调用
private Singleton() { }
// 内部类中保存单例
private static class LazyHolder {
static final Singleton INSTANCE = new Singleton();
}
// 第一次调用 getInstance 方法,才会导致内部类加载和初始化其静态成员
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
特点:
- 懒惰实例化
- 初始化时的线程安全是由类加载器保障的
2、类加载器
- Bootstrap ClassLoader jre/lib (C++编写,无法直接访问)
- Extension ClassLoader jre/lib/ext
- Application ClassLoader classpath (系统类加载器)
- 自定义类加载器 自定义
特点:
- 每个类加载器都有一个独立的类命名空间
- 任意一个类,都需要类本身+加载此类的类加载器共同确定唯一性
**双亲委派模式
工作过程:一个类加载器收到了加载类的请求,他首先把这个请求委派给上级类加载器去完成,每层都是如此,直到传递给顶层的启动类加载器,只有当上层类加载器无法完成加载请求(搜索范围中没有这个类)时,下层加载器才会尝试自己加载。
优点:
- 如Object类,他的位置只能被最顶层的启动类加载器加载,所以无论哪个类加载器要加载这个类,最终都是由启动类加载器加载,这保证了Object类在各种类加载器环境中的都是同一个。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先查找该类是否已经被该类加载器加载过了
Class<?> c = findLoadedClass(name);
// 如果没有被加载过
if (c == null) {
long t0 = System.nanoTime();
try {
// 看是否被它的上级加载器加载过了 Extension 的上级是Bootstarp,但它显示为null
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) {
// 如果还是没有找到,先让拓展类加载器调用 findClass 方法去找到该类,如果还是没找到,就抛出异常
// 然后让应用类加载器去找 classpath 下找该类
long t1 = System.nanoTime();
c = findClass(name);
// 记录时间
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
==在加载JDBC数据库驱动时,使用的是应用程序类加载器(没使用启动类加载器,打破了双亲委派模式)