前言
- 了解 类加载器 有利用在类初始化时进行一些功能操作
- 本文全面讲解类加载器,希望你们会喜欢。
Carson带你学JVM系列文章,具体如下:
Carson带你学JVM:这是一份全面 & 详细的JVM学习指南
Carson带你学JVM:图文解析Java虚拟机内存结构
Carson带你学JVM:Java对象的创建、内存布局 & 访问定位全过程解析
Carson带你学JVM:Java对象如何判断存活原则-引用计数法 & 引用链法
Carson带你学JVM:这是一份全面 & 详细的垃圾收集算法(GC)讲解攻略
Carson带你学JVM:常见的垃圾收集器学习指南
Carson带你学JVM:类加载的全过程解析
Carson带你学JVM:你真的了解类加载器吗?(含双亲委派模型)
Carson带你学JVM:方法分派模型-静态分派、动态分派
目录
1. 作用
- 实现类加载的功能
- 确定被加载类 在
Java
虚拟机中 的 唯一性
下面我会进行详细讲解。
1.1 实现类加载的功能
即实现 类加载过程中“加载”环节里 “通过类的全限定名来获取定义此类的二进制字节流” 的功能
具体请看我写的文章:(JVM)Java虚拟机:类加载的5个过程
1.2 确立 被加载类 在 Java
虚拟机中 的 唯一性
- 确定 两个类是否 相等 的依据:是否由同一个类加载器加载
- 若 由同一个类加载器 加载,则这两个类相等;
- 若 由不同的类加载器 加载,则这两个类不相等。
即使两个类来源于同一个
Class
文件、被同一个虚拟机加载,这两个类都不相等
- 在实际使用中,是通过下面方法的返回结果(
Boolean
值)进行判断:-
Class
对象的equals()
方法 -
Class
对象的isAssignableFrom()
方法 -
Class
对象的isInstance()
方法
-
当然也会使用instanceof关键字做对象所属关系判定等情况
- 实例说明
下面我将举个例子来说明:
public class Test {
// 自定义一个类加载器:myLoader
// 作用:可加载与自己在同一路径下的Class文件
static ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (!name.equals("com.carson.Test"))
return super.loadClass(name);
try {
String fileName = name.substring(name.lastIndexOf(".") + 1)
+ ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(fileName);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
public static void main(String[] args) throws Exception {
Object obj = myLoader.loadClass("com.carson.Test");
// 1. 使用该自定义类加载器加载一个名为com.carson.Test的类
// 2. 实例化该对象
System.out.println(obj);
// 输出该对象的类 ->>第一行结果分析
System.out.println(obj instanceof com.carson.Test);
// 判断该对象是否属于com.carson.Test类 ->>第二行结果分析
}
}
<-- 输出结果 -->
class com.carson.Test
false
// 第一行结果分析
// obj对象确实是com.carson.Test类实例化出来的对象
// 第二行结果分析
// obj对象与类com.huachao.Test做所属类型检查时却返回了false
// 原因:虚拟机中存在了两个Test类(1 & 2):1是由系统应用程序类加载器加载的,2是由我们自定义的类加载器加载
// 虽然都是来自同一个class文件,但由于由不同类加载器加载,所以依然是两个独立的类
// 做对象所属类型检查结果自然为false。
2. 类加载器的类型
- 类加载器的类型数量分别从
Java
虚拟机 &Java
开发者的角度来看,如下图
- 下面主要讲解从
Java
开发者角度看的类加载器,即讲解:- 启动类加载器
- 扩展类加载器
- 应用程序类加载器
2.1 启动类加载器(Bootstrap ClassLoader)
- 作用
负责加载以下类:- 存放在
<JAVA_HOME>\lib
目录中的类 - 被
-Xbootclasspath
参数所指定路径中、并且是被虚拟机识别的类库
- 存放在
仅按文件名识别,如:
rt.jar
,名字不符合的类库即使放在lib目录中也不会被加载
- 特别注意
- 启动类加载器 无法 被
Java
程序直接引用 - 用户在编写自定义类加载器时,若需把 加载请求 委派 给 引导类加载器,直接使用
null
代替即可,如java.lang.ClassLoader.getClassLoader()
方法所示:
- 启动类加载器 无法 被
@CallerSensitive
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
2.2 扩展类加载器(Extension ClassLoader)
-
作用:
负责加载以下类:-
<JAVA_HOME>\lib\ext
目录中的类库 - 被
java.ext.dirs
系统变量所指定的路径中的所有类库
-
-
特别注意
- 由
sum.misc.Launcher$ExtClassLoader
类实现 - 开发者可以直接使用扩展类加载器
- 由
2.3 应用程序类加载器(Application ClassLoader)
作用:
负责加载 用户类路径(ClassPath
)上所指定的类库-
特别注意
- 也称为系统类加载器,因为该类加载器是
ClassLoader
中的getSystemClassLoader()
方法的返回值 - 由
sum.misc.Launcher$AppClassLoader
类实现 - 开发者可以直接使用该类加载器
- 若开发者 没 自定义类加载器,程序默认使用该类加载器
- 也称为系统类加载器,因为该类加载器是
- 各种类加载器的使用并不是孤立的,而是相互配合使用
- 在
Java
虚拟机中,各种类加载器 配合使用 的 模型(关系)是 双亲委派模型
下面我将详细讲解。
3. 双亲委派模型
3.1 模型说明
3.2 工作流程讲解
- 双亲委派模型的工作流程代码实现在
java.lang.ClassLoader的loadClass()
中 - 具体如下
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
// 检查需要加载的类是否已经被加载过
if (c == null) {
try {
// 若没有加载,则调用父加载器的loadClass()方法
if (parent != null) {
c = parent.loadClass(name, false);
}else{
// 若父类加载器为空,则默认使用启动类加载器作为父加载器
c=findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 若父类加载器加载失败会抛出ClassNotFoundException,
//说明父类加载器无法完成加载请求
}
if(c==null){
// 在父类加载器无法加载时
// 再调用本身的findClass方法进行类加载
c=findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
步骤总结:若一个类加载器收到了类加载请求
- 把 该类加载请求 委派给 父类加载器去完成,而不会自己去加载该类
每层的类加载器都是如此,因此所有的加载请求最终都应传送到顶层的启动类加载器中
- 只有当 父类加载器 反馈 自己无法完成该加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会自己去加载
3.3 优点
Java
类随着它的类加载器一起具备了一种带优先级的层次关系
- 如:类
java.lang.Object
(存放在rt.jar
中)在加载过程中,无论哪一个类加载器要加载这个类,最终需委派给模型顶端的启动类加载器进行加载,因此Object
类在程序的各种类加载器环境中都是同一个类。- 若没有使用双亲委派模型(即由各个类加载器自行去加载)、用户编写了一个
java.lang.Object
的类(放在ClassPath
中),那系统中将出现多个不同的Object
类,Java体系中最基础的行为就无法保证
在讲完系统的类加载器后,下面我将讲解如何根据需求自定义类加载器。
4. 自定义类加载器
主要是通过继承自ClassLoader类 从而自定义一个类加载器
MyClassLoader.java
// 继承自ClassLoader类
public class MyClassLoader extends ClassLoader {
// 类加载器的名称
private String name;
// 类存放的路径
private String classpath = "E:/";
MyClassLoader(String name) {
this.name = name;
}
MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}
@Override
public Class<?> findClass(String name) {
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
public byte[] loadClassData(String name) {
try {
name = name.replace(".", "//");
System.out.println(name);
FileInputStream is = new FileInputStream(new File(classpath + name
+ ".class"));
byte[] data = new byte[is.available()];
is.read(data);
is.close();
return data;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
下面我将用一个实例来说明如何自定义类加载器 & 使用。
步骤1:自定义类加载器MyClassLoader
MyClassLoader.java
// 继承自ClassLoader类
public class MyClassLoader extends ClassLoader {
// 类加载器的名称
private String name;
// 类存放的路径
private String classpath = "E:/";
MyClassLoader(String name) {
this.name = name;
}
MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}
@Override
public Class<?> findClass(String name) {
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
public byte[] loadClassData(String name) {
try {
name = name.replace(".", "//");
System.out.println(name);
FileInputStream is = new FileInputStream(new File(classpath + name
+ ".class"));
byte[] data = new byte[is.available()];
is.read(data);
is.close();
return data;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
步骤2:定义待加载的类
TestObject.java
public class TestObject {
public void print() {
System.out.println("hello DiyClassLoader");
}
}
步骤3:定义测试类
Test.java
public class Test {
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
MyClassLoader cl = new MyClassLoader("myClassLoader");
// 步骤1:创建自定义类加载器对象
Class<?> clazz = cl.loadClass("com.carson.TestObject");
// 步骤2:加载定义的测试类:myClassLoader类
TestObject test= (TestObject) clazz.newInstance();
// 步骤3:获得该类的对象
test.print();
// 输出
}
}
// 输出结果
hello DiyClassLoader
4. 总结
- 本文全面讲解类加载器
- 接下来我会对Java虚拟机(JVM)进行详细的分析,欢迎关注Carson_Ho的简书,不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度。
请点赞!因为你的鼓励是我写作的最大动力!
Carson带你学JVM系列文章,具体如下:
Carson带你学JVM:这是一份全面 & 详细的JVM学习指南
Carson带你学JVM:图文解析Java虚拟机内存结构
Carson带你学JVM:Java对象的创建、内存布局 & 访问定位全过程解析
Carson带你学JVM:Java对象如何判断存活原则-引用计数法 & 引用链法
Carson带你学JVM:这是一份全面 & 详细的垃圾收集算法(GC)讲解攻略
Carson带你学JVM:常见的垃圾收集器学习指南
Carson带你学JVM:类加载的全过程解析
Carson带你学JVM:你真的了解类加载器吗?(含双亲委派模型)
Carson带你学JVM:方法分派模型-静态分派、动态分派