类加载
定义:
当 用到某个类 时,首先需要通过 类加载器 把类加载到JVM上
组成:
- 引导类加载器 —— 加载lib目录
- 扩展类加载器 —— 加载ext目录
- 应用程序类加载器 —— 加载ClassPath目录 【用户写的】
- 自定义加载器 —— 加载用户自定义目录
※ 类运行加载全过程图
类加载流程
定义:
JVM加载类的流程如下:
步骤 | 作用 |
---|---|
加载 | 字节码load内存,生成 java.lang.Class对象 |
验证 | 校验字节码文件的正确性 |
准备 | 静态变量分配内存,赋默认值 |
解析 | 符号引用 (英文等符号)替换为 直接引用 (内存地址) 【静态链接】 |
初始化 | 静态变量 初始化指定值、执行 静态代码块 |
特点:
懒加载机制,用到再加载
类加载器
定义:
Java里有如下四种类加载器,除了引导类加载器,其他类加载器的父类均为ClassLoader
类
- 引导类加载器
- 扩展类加载器
- 应用程序类加载器
- 自定义加载器
引导类加载器
定义:
Bootstrap类加载器,用于加载java核心类库
初始化流程:
通过C++创建一个引导类加载器 实例
Launcher类
定义:
它由引导类加载器加载,然后加载了扩展类加载器和应用类加载器
Launcher类的getClassLoader()
方法,返回是==应用类加载器==
扩展类加载器
定义:
Ext类加载器,用于加载java的一些扩展类库
初始化流程:
-
引导类加载器 加载了Launcher类实例
-
Launcher类的构造方法中创建了扩展类加载器
源码:
//Launcher的构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//构造扩展类加载器,在构造的过程中将其父加载器设置为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
}
// 通过双重检测锁来构造扩展类加载器 DCL
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
// DCL
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader();
}
}
}
return instance;
}
应用类加载器
定义:
应用类加载器,用于加载我们的应用程序
初始化流程:
-
引导类加载器 加载了Launcher类实例
-
Launcher类的构造方法中创建了应用类加载器,并将父加载器 【不是父类】指定为 引导类加载器 【用于双亲委派机制】
//Launcher的构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
//构造扩展类加载器,在构造的过程中将其父加载器设置为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
try {
//构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader 【var1】
//getClassLoader() 获得应用类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
}
双亲委派机制
定义:
JVM类加载器在加载类时,是根据双亲委派机制来进行加载的
简单说,子加载器不先加载类,委托父加载器进行加载,如果父加载器加载不了该类,才让子加载器加载。
源码:
//ClassLoader的loadClass方法,里面实现了双亲委派机制
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(c == null)
if (parent != null) {
// 如果当前加载器父加载器不为空,则委托父加载器加载该类
c = parent.loadClass(name, false);
} else {
// 如果当前加载器的父加载器为空,则委托引导类加载器加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 找不到该类
}
if (c == null) {
long t1 = System.nanoTime();
//都会调用URLClassLoader的findClass()方法在加载器的类路径里查找并加载该类
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;
}
}
小结:
双亲委派机制 在ClassLoader.loadClass()
方法中已经定义好,通过之前Launcher
类设置的parent
的关系,让父加载器先加载;Ext扩展类加载器没有parent,通过方法 findBootstrapClassOrNull()
委托引导类加载器加载
为什么要设计双亲委派机制?
- 沙箱安全机制 —— 与 rt.jar 等同包同名的类,不会被加载,防止核心API类库被修改
- 避免类的重复加载 —— 父加载器已经加载过了的类,没必要让子类重复加载一次,确保 被加载类的唯一性
全盘负责委托机制
定义:
全盘负责是指:一个类被一个ClassLoader加载过后,该类所依赖及引用的类都由这个ClassLoader加载 【除非显式指定ClassLoader】
自定义类加载器
定义:
Java支持自定义类加载器,条件有二:
- 继承
java.lang.ClassLoader
类 - 重写
findClass()
方法
例子:
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
// 重要方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 将.class类文件加载到内存
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
// MyClassLoader的父类是AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
//D盘创建 test/com/summer/jvm 几级目录,将User类的复制类User1.class丢入该目录
Class clazz = classLoader.loadClass("com.summer.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
注意:
如果父加载器有com.summer.jvm.User1
类,那么不会用自定义类加载器
此时需要重写loadClass()
方法,修改双亲委派机制的逻辑,让自定义类不要往上委派