前言
从宏观角度来看,JVM中实例化一个对象的基本过程如下:
类加载器主要负责将class从本地磁盘加载到内存中,ClassLoader有一种层次关系,但不是传统意义上的继承。层次关系如下:
双亲委派机制
BootstrapClassLoader用于加载lib目录下的核心库,比如String就由此加载。
ExtClassLoader加载一些拓展类,至于哪些是拓展类,文章的后面会做验证。
AppClassLoader加载classpath下的类。
为啥需要这么多加载器,而且不同的加载器有着不同的管辖范围?这就引出一个概念:”双亲委派机制“
当JVM加载一个类的时候,不会直接使用AppClassLoader,而是交给ExtClassLoader加载,ExtClassLoader加载前也会尝试用BootstrapClassLoader加载。
总的来说,就是保持一个原则:保证高层次的类加载器优先加载。
这是一种典型的特权行为,通过优先级的划分,来使得核心类库优先被加载。
这样核心类库无法被应用程序篡改,保证了JVM环境的安全。
Launcher
main()方法是Java程序的入口,而Launcher是JRE中用于启动main()方法的类,其内部定义了ExtClassLoader与AppClassLoader,负责初始化工作。
ExtClassLoader
继承体系同AppClassLoader,放在下面说了。
主要的创建逻辑在 createExtClassLoader()
获取ExtClassLoader负责的加载的class文件目录
可以打印一下
public class ClassLoaderDemo {
public static void main(String[] args) throws ClassNotFoundException {
String bootstrapUrl = System.getProperty("java.ext.dirs");
String[] split = bootstrapUrl.split(";");
for (String s : split) {
System.out.println(s);
}
}
}
结果
可以看出是jre下的 ext包
AppClassLoader
继承体系
继承自URLClassLoader。通常情况下,没有一重继承,就会多一层含义、多一个作用。
SecureClassLoader
大概意思就是,AppClassLoader是根据路径来加载类资源文件的。
/jdk/bin 不以斜杠结尾,就表示加载一个名为bin的jar包
/jdk/bin/ 以斜杠结尾,则表示加载这个文件夹里所有的资源。
回到AppClassloader类本身,我们在Laucher的构造方法中看到,获取AppClassLoader实力时,会给他传一个ExtClassLoader
仔细看这个构造方法,将var2(也就是ExtClassLoader)传给了父类的构造
父类又继续向上传
直到传到了ClassLoader
保存了下来,也就是说AppClassLoader中有一个变量parent,其值为ExtClassLoader。
下面到了最关键的load环节:
在ClassLoader方法内有一个很重要的私有方法,loadClassInternal
虚拟机会调用私有方法loadClassInternal,而loadClassInternal唯一做的事就是调用loadClass方法。
在经过一些类的合法性校验后,调用父类的loadClass加载类文件。ClassLoader类中的loadClass便是”双亲委派机制“的实现。
我们在自定义ClassLoader时候,必须继承ClassLoader类。其次,官方不建议直接重写loadClass方法,为了遵循双亲委派机制,我们应该保留loadClass中的代码,重写findClass方法。
当loadClass发现BootStrapClassLoader和ExtClassLoader无法加载类时,会调用findClass方法。
BootstrapClassLoader
BootstrapClassLoader显得比较神秘一点,它由C++实现,是JVM自身的一部分,我们看不到其具体的实现。
但依然可以通过一些方法 来捕捉它
String的ClassLoader理论上应该是BootstrapClassLoader
但是打印出来却是null。
我们结合ExtClassLoader的构造函数分析
第二个参数传递的是他的 父 ClassLoader(在AppClassLoader小标题下已经分析了,不重复),也传了一个null。
结合起来看,结论就出来了。
BootstrapClassLoader抽象层级较高,并且由C++实现,无法拓展,如果尝试获取某个类的ClassLoader返回值是null,则表示由BootstrapClassLoader加载。
进一步验证
我定义一个UserInfo类用于测试,尝试嵌套调用它的ClassLoader
public class ClassLoaderDemo {
public static void main(String[] args) throws ClassNotFoundException {
System.out.println(UserInfo.class.getClassLoader());
System.out.println(UserInfo.class.getClassLoader().getParent());
System.out.println(UserInfo.class.getClassLoader().getParent().getParent());
}
}
结果中体现的层次关系就很明显了
其实Launcher中有一个变量 sun.boot.class.path 存放的就是BootStrapClassLoader管辖的范围
public class ClassLoaderDemo {
public static void main(String[] args) throws ClassNotFoundException {
String property = System.getProperty("sun.boot.class.path");
for (String s : property.split(";")) {
System.out.println(s);
}
}
}
控制台输出如下
可见主要是jre lib目录下的一些jar包
其他
由于篇幅原因,自定义ClassLoader、字节码加密等知识总结就不准备放一篇文章里了。
如有错误,欢迎批评指正!