JVM专题学习之类加载器(二)
类加载器
三层类加载器
1.启动类加载器-BootstrapClassLoader
AppClassLoader负责加载核心类,存放在lib目录下的jar包或class文件。
2.扩展类加载器-ExtensionClassLoader
ExtensionClassLoader负责加载\lib\ext目录下的jar包或class文件,我们可以将通用性的功能,打成jar包放置到ext
3.应用程序加载器-ApplicationClassLoader
ApplicationClassLoader负责加载用户类路径的所有类库,比如classpath就是由ApplicationClassLoader加载的。
他们三者关系如图所示:
双亲委派机制
工作机制
如果一个类加载器收到了类加载的请求,它会把这个请求委派给父类加载器去完成,每层的类加载都是这个处理逻辑,如果父加载器反馈自己无法完成的时候,子加载类就会自己去尝试加载。
为什么这样设计一个机制
其实是为了保证基础类库的稳定性,安全性。基础类rt.jar
有很多我们开发中经常用到的类比如String
类,假设没有设计这个机制,我们在项目中同样声明一个String类那岂不是被替换掉了。由此可以看出,此机制设计的目的在于保证基础类库的稳定性和安全性。
学习源码
//java.lang.ClassLoader
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//1.首先,检查这个类是否被加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
//2.当前这个类未被加载
//2.1 让上层去负责加载类
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果存在上层的类加载器
//就交给父加载器去加载
c = parent.loadClass(name, false);
} else {
//如果不存在上层的类加载器
//就交给BootstrapClassLoader来负责加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
//2.1 说明父加载器未加载到类
long t1 = System.nanoTime();
//交给自己来加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
自定义类加载器
1.代码构思
建好一个Order类,然后在Main方法中new一个Order类。在项目文件夹中找到Order.class文件。复制到这个路径:G:\com\classLoader\Order.class,然后删掉Order.java文件,因为项目中的类会优先使用ApplicationClassLoader去加载,达不到我们自定义类加载的效果。
- 继承java.lang.ClassLoader
- 重写findClass方法
- 自定义一个方法,通过自定义类加载器,加载磁盘上的class文件
注意:包名要保持一致,测试代码中的包名要和磁盘中的包名保持一致。要不然会报错
2.代码实现
package com.classLoader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{
//指定从哪加载类
private String classPath;
public MyClassLoader(String classPath){
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
//1.根据类全名获取到对应的字节数组
byte[] data = loadByte(className);
//2.通过JDK提供的API,将字节数组转换为Classs对象
return defineClass(className,data,0,data.length);
}
private byte[] loadByte(String className) {
//1.将类全名转换为完整类路径
className = className.replaceAll("\\.","/");
//G:\javaHomeWork\jvmDemo\src\main\java\com\Demo\com.classLoader
StringBuilder stringBuilder = new StringBuilder(classPath);
stringBuilder.append(className);
stringBuilder.append(".class");
System.out.println(stringBuilder);
//2.采用文件字节流读取文件内容,并将其转换为字节数组
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(stringBuilder.toString());
//3.创建字节数组,用于存放文件内容
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
return data;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
3.测试代码
package com.classLoader;
public class MyClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader myClassLoader = new MyClassLoader("G:/");
Class<?> clazz = myClassLoader.loadClass("com.Person");
//AppClassLoader MyClassLoader
System.out.println(clazz.getClassLoader());
System.out.println(myClassLoader.getParent());
}
}
4. 运行结果
5. 自定义类加载器关系
6. 打破双亲委派机制
我们在不删除Order类的情况下去使用我们的自定义类加载器去加载Order类
思路如下:
- loadClass方法规定了双亲委派机制的工作模式
- 只需要重写loadClass方法
代码实现
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
if (name.startsWith("com.classLoader")) {
//如果是我们自己项目自定义的类,则交给自己来加载
c = findClass(name);
}else{
//交给父类加载器去加载
//保证核心类库的唯一性
c = this.getParent().loadClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
运行结果
7.Tomcat的类加载机制
tomcat如何保证每个应用的类库的独立的?
不同的类加载器实例加载的类是隔离的
tomcat为每个web项目创建一个类加载实例,webappClassLoader