ClassLoader层级关系
每个类加载器都有自己预先定义好的加载class的路径。
1、BootStrap class loader(根类加载器)
负责加载jdk/libs/rt.jar中所有的class。它是最上层的classLoader,如果打印它的父classLoader,会显示null。
2、Extension class loader(扩展类加载器)
加载jdk/lib/ext中的class
3、System or Application class loader (系统/应用类加载器)
加载classpath指定的路径下的class
类加载器 | 表示 |
---|---|
根类加载器 | null |
扩展类加载器 | sun.misc.Launcher$ExtClassLoader |
系统/应用类加载器 | sun.misc.Launcher$AppClassLoader |
双亲委托模型
一句话概括就是:父亲能找到的class,我就懒得找了,直接继承,多省事啊。
至于为什么叫双亲委托模型,知乎上已有吐槽。
Java 中的双亲委派的“双”怎么理解 ?
https://www.zhihu.com/question/288949359
如果将class放到根类加载器加载class的目录下,那么就轮不到系统/应用类加载器。
如何查看类加载器的加载class目录?
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
输出
C:\Program Files\Java\jdk1.8.0_231\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_231\jre\classes
换行
C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
换行
C:\Program Files\Java\jdk1.8.0_231\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\rt.jar;D:\github_projects\jvm\jvm-base\target\classes;
类的名字
ClassLoader加载类的时候,需要先知道class的名字,jdk中class的名字包含包名。
例如java.lang.String。
如果加入包含内部类,名字命名如下,以$隔开。
package com.sss.jvm.unclassified;
public class Test1 {
static class A{
static class A1{
}
}
static class B{
}
public static void main(String[] args) {
System.out.println(Test1.A.class);
System.out.println(Test1.A.A1.class);
System.out.println(Test1.B.class);
}
}
输出
class com.sss.jvm.unclassified.Test1$A
class com.sss.jvm.unclassified.Test1$A$A1
class com.sss.jvm.unclassified.Test1$B
自定义ClassLoader
自定义ClassLoader必须继承java.lang.ClassLoader抽象类。
里面有几个重要的方法:
1、loadClass:加载class
2、findClass:寻找name指定的class,遵循双亲委托模型,被loadClass调用。如果findClass找不到指定的class,throws java.lang.ClassNotFoundException,如果找到了调用defineClass方法。
3、defineClass:将入参中的byte[]数组转换成Class对象。
package com.sss.jvm.classloader.test7;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 自定义类加载器
*/
public class MyClassLoader extends ClassLoader {
private String classLoaderName; //自定义类加载器的名字,仅仅起标识作用
private final String fileExtension = ".class";
private String path;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public MyClassLoader(){
}
public MyClassLoader(String classLoaderName){
super(); //将系统类加载器当作该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
/**
* 给自定义加载器指定父加载器
* @param parent
* @param classLoaderName
*/
public MyClassLoader(ClassLoader parent, String classLoaderName){
super(parent);//显式指定该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
/**
*
* @param name
* @return
*/
private byte[] loadClassData(String name){
ByteBuffer data = null;
FileChannel fileChannel;
FileInputStream fileInputStream;
try{
name = name.replace(".","/");
fileInputStream = new FileInputStream(this.path + name + this.fileExtension);
fileChannel = fileInputStream.getChannel();
data = ByteBuffer.allocate((int) fileChannel.size());
fileChannel.read(data);
}catch(Exception ex){
ex.printStackTrace();
} finally {
//try-with-resource
/*fileChannel.close();
fileInputStream.close();*/
}
return data.array();
}
/**
* 寻找name指定的class,遵循双亲委托模型,被loadClass调用。
* @param name
* @return
*/
@Override
protected Class<?> findClass(String name) {
byte[] data = this.loadClassData(name);
return this.defineClass(name,data,0,data.length);
}
}
ClassLoader的基本原则
Java的类加载器在运行期加载class。遵循3个基本原则:委托、可见性、唯一性。
委托性:加载class的请求会先转发给父加载器,父加载器找不到class才会由自己加载。
可见性:子加载器能看到父加载器加载的所有class,反之不行。
唯一性:父子加载器保证某个class只会加载一次,不会出现多个。
测试一
用上面的自定义类加载器测试一:
public static void main(String[] args) throws Exception {
MyClassLoader loader1 = new MyClassLoader("自定义的类加载器");
loader1.setPath("D:/");
Class<?> clazz = loader1.loadClass("com.sss.jvm.dump.JvmDemo01");
Object object = clazz.newInstance();
System.out.println(object.hashCode());
System.out.println(object.getClass().getClassLoader());
System.out.println(object.getClass().getClassLoader().hashCode());
System.out.println("-----------------------------------");
MyClassLoader loader2 = new MyClassLoader("自定义的类加载器");
loader2.setPath("D:/");
clazz = loader2.loadClass("com.sss.jvm.dump.JvmDemo01");
object = clazz.newInstance();
System.out.println(object.hashCode());
System.out.println(object.getClass().getClassLoader());
System.out.println(object.getClass().getClassLoader().hashCode());
}
idea运行,输出肯定是:可以看到由系统/应用类加载器加载。使用2次类加载器创建类,两次类加载器都是AppClassLoader。因为根据双亲委托模型,根本轮不到我们自己创建的类加载发挥作用。
834600351
sun.misc.Launcher$AppClassLoader@18b4aac2
414493378
-----------------------------------
471910020
sun.misc.Launcher$AppClassLoader@18b4aac2
414493378
但是如果将target里将要加载的目标class删掉,再次运行:
java.io.FileNotFoundException: D:\com\sss\jvm\dump\JvmDemo01.class (系统找不到指定的路径。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at com.sss.jvm.classloader.test7.MyClassLoader.loadClassData(MyClassLoader.java:54)
at com.sss.jvm.classloader.test7.MyClassLoader.findClass(MyClassLoader.java:75)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at com.sss.jvm.classloader.test7.MyClassLoader.main(MyClassLoader.java:83)
Exception in thread "main" java.lang.NullPointerException
at com.sss.jvm.classloader.test7.MyClassLoader.loadClassData(MyClassLoader.java:65)
at com.sss.jvm.classloader.test7.MyClassLoader.findClass(MyClassLoader.java:75)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at com.sss.jvm.classloader.test7.MyClassLoader.main(MyClassLoader.java:83)
再将目标类手动拷贝到D盘指定文件夹下运行:就会发现我们自定义的类加载器起作用了。
531885035
com.sss.jvm.classloader.test7.MyClassLoader@31befd9f
834600351
-----------------------------------
135721597
com.sss.jvm.classloader.test7.MyClassLoader@548c4f57
1418481495
测试二
还是用上面自定义类加载器进行测试二:
public static void main(String[] args) throws Exception {
MyClassLoader2 loader1 = new MyClassLoader2("自定义的类加载器");
loader1.setPath("D:/");
Class<?> clazz = loader1.loadClass("com.sss.jvm.dump.JvmDemo01");
Object object = clazz.newInstance();
System.out.println(object.hashCode());
System.out.println(object.getClass().getClassLoader());
System.out.println(object.getClass().getClassLoader().hashCode());
System.out.println("-----------------------------------");
MyClassLoader2 loader2 = new MyClassLoader2(loader1,"自定义的类加载器");
loader2.setPath("D:/");
clazz = loader2.loadClass("com.sss.jvm.dump.JvmDemo01");
object = clazz.newInstance();
System.out.println(object.hashCode());
System.out.println(object.getClass().getClassLoader());
System.out.println(object.getClass().getClassLoader().hashCode());
}
删掉idea工具target里的目标class,运行:
531885035
com.sss.jvm.classloader.test7.MyClassLoader2@31befd9f
834600351
-----------------------------------
1418481495
com.sss.jvm.classloader.test7.MyClassLoader2@31befd9f
834600351
情况和测试一有所不同。测试二是将loader1作为了loader2的父加载器,所以JvmDemo01类的类加载器是同一个。而测试一,loader1和loader2无关系,两者均能加载目标JvmDemo01类。由此也可证明,只要类加载器不同,是可以存在同名的类的。