有哪些ClassLoader
- Bootstrap ClassLoader
- Extension ClassLoader
- App ClassLoader
Bootstrap ClassLoader
- 类名:
BootstrapClassLoader
- Bootstrap ClassLoader不是Java Class(其他的ClassLoader,包括Extension ClassLoader都是Java类),而是JVM虚拟机中实现的类加载器,用C或者C++写的。
- 负责加载
$JAVA_HOME/jre/lib/
目录下的class,如$JAVA_HOME/jre/lib/rt.jar。这些classes可以称为"Bootstrap classes" - 通过非标准参数
-Xbootclasspath
可以改变Bootstrap classes的位置。但是绝大部分情况下,是没必要的
Extension ClassLoader
- 类名:
ExtClassLoader
,继承自ClassLoader
- 加载
$JAVA_HOME/jre/lib/ext/
目录下的class - 如果ext目录下的jar包中有相同路径的class,那么这个class是没法被加载的
如:
image.pngsmart-extension1_0.jar 有class: smart.extension.Smart
smart-extension1_1.jar 同样有class: smart.extension.Smart
那么,smart.extension.Smart是undefined
App ClassLoader
- 类名:
AppClassLoader
,父ClassLoader是ExtClassLoader
- 加载classpath中的class,或者是
java.class.path
属性定义目录下的class。 -
java.class.path
的默认值是'.',也就是当前目录
如果com.mypackage.MyClass在/classes/目录下,那么/classes/目录必须在classpath中。如果该class在myclasses.jar中,那么myclasses.jar也必须在classpath中。
那么,classpath如何指定呢?
- 默认是".",也就是当前目录
- 设置CLASSPATH环境变量
- 通过-cp或者-classpath参数指定。这种方式会覆盖默认值和CLASSPATH设定的值
- 通过-jar参数指定。这个会覆盖其他所有的值。
如:
java -jar spring-boot.jar
没有通过-cp/-classpath
指定classpath,也没有-Djava.class.path
参数,因此,默认是当前目录,也就是加载spring-boot.jar中的class
查看ClassLoader
Class
类中提供了getClassLoader()
方法来查看某个类的ClassLoader。简单的例子如下:
public class App
{
public static void main( String[] args ) {
System.out.println("App's ClassLoader is: " + App.class.getClassLoader());
ClassLoader parent = App.class.getClassLoader().getParent();
System.out.println("AppClassLoader's parent is:" + parent);
InnerClass innerClass = new InnerClass();
System.out.println("InnerClass's ClassLoader is:" + innerClass.getClass().getClassLoader());
}
}
输出为:
App's ClassLoader is: sun.misc.Launcher$AppClassLoader@279f2327
AppClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@452b3a41
InnerClass's ClassLoader is:sun.misc.Launcher$AppClassLoader@279f2327
Java类加载机制
英文术语
Java类加载机制:Java Class Loading Mechanism
父ClassLoader: parent class loader
所有自定义的ClassLoader都继承自java.lang.ClassLoader
。java.lang.ClassLoader
的构造函数允许指定父ClassLoader。如果不指定,则使用system class loader。
public abstract class ClassLoader {
//parent class loader默认为system class loader
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
//当然,也可以指定parent class loader
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
}
class在loadClass(String name)
方法中被加载。加载逻辑是:
- 如果class已经被加载了,直接return
- 如果class还没被加载,让父ClassLoader去加载。当然,如果父ClassLoader也有parent,则会继续把加载任务交给其父ClassLoader(就是让爸爸的爸爸去实际干活)。
- 如果父ClassLoader找不到class,则自定义的ClassLoader才开始加载class。
- 如果都找不到,抛出
ClassNotFoundException
这就是所谓的“双亲委托加载模型”。
源代码为:
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) {
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);
// 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;
}
}
为什么要委托给parent ClassLoader加载呢?
这是因为,一个Class在JVM中,是通过而元素来标识的,一个是ClassLoader,一个是类自己的全路径类名。
举个例子,有个类com.my.TestClass
和一个自定义的加载器TestClassLoader
(如不指定父ClassLoader,那么其parent为AppClassLoader
)
- 如果
TestClass
由TestClassLoader
加载,这标识为<TestClassLoader, TestClass>
- 如果
TestClass
由AppClassLoader
加载,这标识为<AppClassLoader, TestClass>
那么问题来了,同一个类com.my.TestClass
由不同的ClassLoader加载,对于JVM来讲,是不同的类!
假如有类似如下代码:
TestClass A = ...; //由TestClassLoader加载
TestClass B = ...; //由AppClassLoader加载
A = B; //报错!抛出ClassCastException
上面的类由不同的ClassLoader加载,执行到A=B
时,会出现类似如下报错:
java.lang.ClassCastException: com.my.TestClass can not be cast to com.my.TestClass
因此,同一个Class由同一个ClassLoader加载是非常必要的。优先由parent ClassLoader加载就可以避免上述问题。
网友评论