什么是类的加载
我们平时所编写的“xx.java”文件需要经过我们所知的java编译器(javac)编译成“xx.class”文件,这个文件存放着编译后jvm指令的的二进制信息。而**当我们需要用到某个类时,jvm将会加载它,并在内存中创建对应的class对象,这个过程称之为类的加载。**过程如下:
类的加载、连接、初始化
1. 加载
通过类的包名和雷鸣查找到此类的字节码文件,将xx.class文件中的二进制数据读入到jvm内存,并存入其中的方法区内,然后利用字节码文件创建一个class对象存入到堆之中,用来封装类的数据结构等。
连接(验证、准备、解析):
2. 连接包括三步:
验证:确保被加载类的正确性,是否对jvm有害
准备:为**类的静态变量(static)**分配内存并将其赋值为默认变量(非赋值)eg:static int a = 1;(为a分配内存并设置为0,将a赋值为1的动作是在初始化过程中完成的,而final static int a = 1赋值是在javac过程中完成的。)
解析:把类中的符号引用转化为直接引用,符号引用:我们所写的int a 就是符号引用,直接引用:目标的指针、偏移量内存中的引用,类似c/c++中的指针。
3. 初始化
如果有父类,此时加载父类并进行初始化,静态变量、成员变量等,再加载子类。
类加载器
类的加载是由类加载器完成的。类加载器分为jvm自带的类加载器和用户自定义的类加载器。
jvm内置加载器():
启动类加载器Bootstrap ClassLoader
扩展类加载器Extention ClassLoader
系统类加载器System(Application) ClassLoader(应用类加载器)
用户自定义加载器:
java.lang.ClassLoader的子类实例。
Bootstrap ClassLoader
最底层的类加载器,是jvm的一部分,由c++实现,没有父加载器
主要负责加载由系统属性sun.boot.class.path指定的路径下的核心类库(jre/lib),出于安全考虑该类加载器只加载只加载java,javax,sun相关包里的类。
Extention ClassLoader
sun公司实现的sun.misc.Launcher$ExtClassLoader类(jdk8),由java编写,负责加载jre/lib/ext目录下的类库或者由系统变量“java.ext.dirs”指定的目录下的类库。父加载器是Bootstrap ClassLoader(非继承)。
System(Application) ClassLoader
一个纯java类,sun公司实现的sun.misc.Launcher$AppClassLoader类(jdk8),父加载器是扩展加载器(Extention ClassLoader)(非继承),负责从classpath环境变量或者java.class.path所指定的目录中加载类。是用户自定义的类加载器的默认父加载器。一般情况下是程序默认类加载器,可通过*ClassLoader.getSystemClassLoader()*直接获得。
双亲委派模型
双亲委派机制
当需要使用该类时,才会将它的class文件加载到内存生成class对象(按需加载),加载时,使用的是双亲委派模式:如果一个类加载器收到了类请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每一层都是如此,因此所有类加载的请求都会传到启动类加载器,只有当父加载器无法完成该请求时,子加载器才去自己加载。(父加载器、子加载器:非继承关系,而是用组合模式来复用父加载器代码)
双亲委派机制的优点:
- 避免类的重复加载,当父加载器加载成功时,就没必要子加载器再去加载
- 安全。防止java核心API不会被随意替换。比如一个网络上的java.lang.Object类,无论哪个类加载器去加载该类,最终都是由启动类加载器进行加载的,因此Object类在程序的各种类加载环境中都是一个类。如果不委托,那么java.lang.Object类存放在classpath中,那么系统中就会出现多个Object类,程序变得很混乱。
ClassLoader
除了启动类加载器Bootstrap ClassLoader,其它类加载器都必须继承java.lang.ClassLoader(abstract)
主要方法:
loadClass();!!!不要覆写此方法
findClass();
defineClass();
resolveClass();
相关使用参考此处
URLClassLoader
java.net.URLClassLoader,扩展了ClassLoader。可以从本地、网络上的指定url加载类。可使用该类作为自定义的类加载器使用。
- 加载磁盘上的class文件:
现在有一class文件 F:/Test01.class
Test01中有默认构造方法:
public Test01(){
System.out.println("test01");
}
此时我们利用URLClassLoader就可以加载磁盘上的class文件:
File file = new File("F:/");
URI uri = file.toURI();
URL url = uri.toURL();
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
System.out.println(classLoader.getParent()); //父加载器
Class aClass = classLoader.loadClass("Test01");
aClass.newInstance();//验证是否调用默认构造方法
对应输出:
可以看到默认构造方法已经被调用。
- 加载网络上的class文件
类似,请读者自行测试
自定义类加载器
方法:继承ClassLoader,并覆写findClass()方法。
自定义文件类加载器
这里同上面一样,存在 F:/Test01.class
我们自定义一个文件类加载器来实现这个类的加载:
import java.io.*;
/*
* 自定义本地类加载器
* director 类所在的目录
*/
public class MyFileClassLoader extends ClassLoader{
private String director;
//默认父加载器为应用类加载器(ApplicationClassLoader)
public MyFileClassLoader(String director) {
this.director = director;
}
//指定父加载器
public MyFileClassLoader(String director, ClassLoader parentClassLoader) {
super(parentClassLoader);
this.director = director;
}
/**
* @param name 类名
* @return 返回字节码对象
*/
@Override
protected Class<?> findClass(String name) {
try {
//将类转化为真正对应class文件的目录
String file = director + File.separator + name.replace(".", "/") + ".class";
//构建输入输出流
InputStream is = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len = -1;
while ((len = is.read(bytes))!= -1) {
baos.write(bytes, 0, len);
}
//读取到的字节码的二进制数据
byte[] data = baos.toByteArray();
is.close();
baos.close();
/*
* name - 预期的 binary name的类
* data - 构成类数据的字节。
* 0 - 类数据中的起始偏移量为 0
* data.length - 类数据的长度
*/
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
MyFileClassLoader myClassLoader = new MyFileClassLoader("F:/");
Class aClass = myClassLoader.loadClass("Test01");
aClass.newInstance();
}
}
网络类加载器
网络类加载器类似(相关流API的使用)
热部署类加载器
**避开双亲委派模式,实现一个类的不同类加载器的加载。**我们已经知道双亲委派模式主要是以loadClass()方法实现的,那么我们可以不使用loadClass()而是用findClass()方法,避开双亲委派,实现一个类的多次加载,达到热部署。
public static void main(String[] args) throws Exception {
MyFileClassLoader myClassLoader = new MyFileClassLoader("F:/");
MyFileClassLoader myClassLoader2 = new MyFileClassLoader("F:/", myClassLoader);
Class aClass = myClassLoader.findClass("Test01");
Class aClass2 = myClassLoader2.findClass("Test01");
System.out.println(aClass.hashCode());
System.out.println(aClass2.hashCode());
}
输出结果:
455896770
1323165413
可以看到哈希码不同,证明一个类被两个加载器都加载了。
来源:CSDN
作者:↣我爱学习喔
链接:https://blog.csdn.net/weixin_45328602/article/details/104558487