在理解ClassLoader之前,我们先回顾下Java的一些相关知识。
基础
1. Java的主要特性
- 平台无关性
- 面向对象
- GC(Java的垃圾回收机制)
- 类库
- 语言特性
- 异常处理
2. 为什么JVM直接将 源码解析成机器码去执行
- 准备工作:每次执行都需要各种检查
- 兼容性:也可以将别的语言解析成字节码
Compile Once,Run Anywhere如何实现
java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码 转换成具体平台上的机器指令。
3. JVM如何加载.class文件
- Class Loader:依据特定格式,加载class文件到内存
- Execution Engine:对命令 进行 解析
- Native Interface:融合不同开发语言的原生库为 Java所用
- Runtime Data Area:JVM内存空间结构模型
4. 反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意 一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java语言的反射机制。
5. 类从编译到执行的过程
- 编译器将
Xxx.java
源文件编译为Xxx.class
字节码文件 - ClassLoader 将字节码转换为JVM中的
Class<Xxx>
对象 - JVM利用
Class<Xxx>
对象实例化为Xxx
对象
谈谈ClassLoader
ClassLoader 在 Java 中有着非常重要的作用,它主要工作在 Class 装载的加载阶段,其主要作用是从系统外部获得 Class 二进制数据流。它是 Java 的核心组件,所有的 Class 都是由 ClassLoader 进行加载的,ClassLoader 负责通过将 Class 文件里的二进制数据流装载进系统,然后交给 Java 虚拟机进行连接、初始化等操作。
1. ClassLoader的种类
- BootStrapClassLoader:C++ 编写,加载核心库 java.*
- ExcClassLoader:Java 编写,加载扩展库 javax.*
- AppClassLoader:Java 编写,加载程序所在目录
- 自定义 ClassLoader:Java 编写,定制化加载
2. 自定义 ClassLoader 的实现
关键函数:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError {
return defineClass(null, b, off, len, null);
}
3. 类的加载方式
- 隐式加载:new
- 显示加载:loadClass,forName等
4. loadClass和forName的区别
类的装载过程
5. loadClass和forName的区别
- Class.forName得到的class是已经初始化完成的
- Classloader.loadClass得到的class是还没有链接的
6. 谈谈类加载器的双亲委派机制
不同类的加载方式和加载路径不同,为了实现分工,各自实现各自的功能,使得逻辑更加的明确,才有这么多共存的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 (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果未发现类,则抛出异常
}
if (c == null) {
// 如果仍未找到,则委托findClass方法去寻找
long t1 = System.nanoTime();
// 自定义的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;
}
}
为什么要使用双亲委派机制去加载类?
为了避免多份同样字节码的加载。(通过逐层检查可以避免多份由各自加载的相同 class文件 )