前言
以下所述,基于java8所写;类加载器通过完全限定名(比如,com.mypak.module1.MyClass
)来加载类,它使类可以动态加载类到jvm中,java并未规定类的位置,可以来自本地文件系统,也可以来自网络。
加载器分类
类加载器分为根加载器(bootstrap classloader)、扩展类加载器(ext classloader)、系统类加载器(system classloader)、自定义类加载器(通常继承java.net.URLClassLoader
,重写findClass()
),它们的关系通常如下。
从图中可以看出,每个类加载器都有一个父加载器(加载器有引用指向父加载器,而不是继承),在加载类时,首先check本身是否已加载,如果已加载,则返回,如果未加载,如果有父加载器则交给父加载器,如果没有父加载器则使用根加载器,如果仍没找到,则本加载器加载类,每一级加载器都执行相同的操作,这种机制称为委托机制,英语是parents delegation model
,翻译过来是双亲委派机制(其实有点词不达意)。
- 由于双亲委派机制,加载
java.lang.String
时会一直往上委派,直到根加载器,而根加载器只会加载java_home/jre/lib/rt.jar
中的java.lang.String
,从而确保自定义的java.lang.String
不会加载到jvm中,而不会让jvm错乱。 - 类加载器+类的完全限定名,组成了在jvm中的唯一标识,如果类加载器不一样,即使类限定名相同,也不相等。
类加载器用抽象类java.lang.ClassLoader
表示,通过loadClass
源码可以看出,它是按照双亲加载的机制来执行的。
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); //首先,check自身加载器是否已加载目标类
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);//有父加载器,则委托给父加载器
} else {
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.
long t1 = System.nanoTime();
c = findClass(name); //调用findClass()方法
// 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;
}
}
根加载器
根加载器主要是用来加载java_home/jre/lib
下的jar包,比如rt.jar
(含有全部java api的类),根加载器用C/C++实现,用null表示,在java代码中无法获取到根加载器。
rt.jar如下
扩展类加载器
用来加载System.getproperty("java.ext.dirs")
也就是java_home/jre/lib/ext`下的jar包,扩展类加载器的父加载器是根加载器。
系统类加载器
用来加载System.getproperty("java.class.path")
也就是我们常说的classpath下的类,此路径下都是应用程序的类,所以也可称为应用程序类加载器,它的父加载器是扩展类加载器,classLoader.getSystemClassLoader()
返回的就是系统类加载器
自定义类加载器
在程序运行时,如需自定义类加载器,通常继承java.net.URLClassLoader
,重写findClass
方法,这样符合双亲委派机制。
实例
系统类加载器–>扩展类加载器–>根加载器
public class MyClassLoader {
public static void main(String[] args) {
// 系统类加载器(应用类加载器)
System.out.println(MyClassLoader.class.getClassLoader());
//扩展类加载器
System.out.println(MyClassLoader.class.getClassLoader().getParent());
//系统类加载器
System.out.println(MyClassLoader.class.getClassLoader().getParent().getSystemClassLoader());
//根加载器
System.out.println(MyClassLoader.class.getClassLoader().getParent().getParent());
//扩展类加载器,java_home/jre/lib/ext/dnsns.jar
System.out.println(DNSNameService.class.getClassLoader());
//根加载器
System.out.println(String.class.getClassLoader());
}
}
输出
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@1b6d3586
null
查看源码得知,ExtClassLoader
加载System.getProperty("java.ext.dirs")
路径下的类,在我的电脑上是D:\jdk1.8.0_211\jre\lib\ext;
static class ExtClassLoader
extends URLClassLoader
{
private static File[] getExtDirs()
{
String str = System.getProperty("java.ext.dirs");
//省略
}
}
AppClassLoader源码得知,它加载System.getProperty("java.class.path")
下的类,在我的系统上是D:\jdk1.8.0_211\jre\lib\ext;
、D:\jdk1.8.0_211\jre\lib\;
还有应用的类,可以看到它包含了ExtClassLoader
的加载路径。
static class AppClassLoader
extends URLClassLoader
{
public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)
throws IOException
{
String str = System.getProperty("java.class.path");
//省略
}
}
类加载器+类全限定名在jvm中是类的唯一标识
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = name.substring(name.lastIndexOf(".")+1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
assert is != null;
byte[] b;
try {
b = new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
try {
Class<?> aClass = new MyClassLoader().findClass("com.jun.javase.MyClassLoader");
System.out.println(aClass+"---"+MyClassLoader.class);
System.out.println(aClass.getClassLoader()+"---"+MyClassLoader.class.getClassLoader());
System.out.println(MyClassLoader.class == aClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出
class com.jun.javase.MyClassLoader---class com.jun.javase.MyClassLoader
com.jun.javase.MyClassLoader@383534aa---sun.misc.Launcher$AppClassLoader@18b4aac2
false
输出false
,因为类加载器不同,即使同一个类,也不相等。