Java类加载机制
JVM架构图
Java文件运行
java在运行时需要把我们写的.java
文件进行编译为.class
文件才可以被JVM
虚拟机运行。
1 | //Main.java |
编译Main.java
:javac Main.java
可以看到生成了Main.class
.
ClassLoader是什么
当程序在运行时,即会调用该程序的一个入口函数(main函数)来调用系统的相关功能,而这些功能都被封装在不同的 class 文件当中,所以经常要从这个 class 文件中要调用另外一个 class 文件中的方法,如果另外一个文件不存在的,则会引发系统异常。而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个 class 文件到内存当中的,从而只有 class 文件被载入到了内存之后,才能被其它 class 所引用。
作用
- 负责将 Class 加载到 JVM 中
- 审查每个类由谁加载(父优先的等级加载机制)
- 将 Class 字节码重新解析成 JVM 统一要求的对象格式
ClassLoader等级加载制度
Java默认提供的三个ClassLoader
BootStrap ClassLoader(引导类加载器)
称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库
Extension ClassLoader(扩展类加载器)
负责加载Java的扩展类库
App ClassLoader(系统类加载器) 默认的加载器。
负责加载应用程序classpath目录下的所有jar和class文件
除了引导类加载器(BootStrap ClassLoader)之外,所有的类加载器都有一个父类加载器,对于系统提供的类加载器来说,系统类加载器(如:AppClassLoader)的父类加载器是扩展类加载器(EtxClassLoader),而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器(User ClassLoader)来说,其父类加载器是加载此类加载器 Java 类的类加载器。一般来说是APPClassLoader。
1 | //Main.java |
运行结果如下
这个说明,AppClassLoader
的父加载器是ExtClassLoader
,但是我们可以看到ExtClassLoader
的父加载器是一个空指针,这代表ExtClassLoader
没有父加载器。
看下源码
1 | static class ExtClassLoader extends URLClassLoader {} |
父加载器不是父类
可以看到ExtClassLoader AppClassLoader
都继承的是URLClassLoader
,父加载器并不是父类。
URLClassLoader
的源码中并没有getParent()
方法,但是在ClassLoader.java
中有
1 | public abstract class ClassLoader { |
可以看到getParent()
返回的是ClassLoader
对象parent
往上看,可以看到,parent的赋值操作是在ClassLoader的构造方法中的。共有两种方式。
- 由外部类创建ClassLoader时直接指定一个ClassLoader为parent
- 由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。
在往下看Launcher类
1 | public class Launcher { |
在这里
1 | ClassLoader extcl; |
AppclassLoader
的parent
是一个ExtClassLoader
类的一个实例。
ExtClassLoader
中也没有对paernt
进行赋值而是调用了URLClassLoder
的构造方法。
1 | public ExtClassLoader(File[] dirs) throws IOException { |
所以AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null。
而Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。
ClassLoader加载类原理
双亲委派模型
ClassLoader使用的是双亲委托机制来搜索加载类的,每个ClassLoader实例都有一个父类加载器(并不是继承的关系),当一个类加载器收到了一个类加载的请求,它并不会直接去加载,而是交给父类加载器去加载,每一个都是这样的。因此所有的加载请求最终都应该传送到BootStrap ClassLoader
里面去,只有当父加载器反馈自己无法完成这个加载请求,那么才会让子类去加载。
好处
- 这样就避免了加载多个相同字节码的问题,例如我们使用
System.out.println()
实际我们需要一个 System 的 Class 对象,并且只需要一份,如果不使用委托机制,而是自己加载自己的,那么就会在一个类加载一次这个字节码。 - 保证基础类不会被篡改而造成安全问题。例如如果不使用这种机制的话,我们自己也可以写一个String类型从而动态替代java核心api中定义的类型。
ClassLoader类核心方法
loadClass
(加载指定的Java类)findClass
(查找指定的Java类)findLoadedClass
(查找JVM已经加载过的类)defineClass
(定义一个Java类)resolveClass
(链接指定的Java类)
自定义类加载器
jdk中的ClassLoader
1 | protected Class<?> loadClass(String name, boolean resolve) |
首先查找JVM是否加载过此类,如果没有就找类加载器的父类加载器,然后让他的父类加载器去加载他,如果它的父加载器
不是null,或者说不是
BootStrap ClassLoader
,就继续执行父加载器的loadClass()
方法,直到它的父类加载器是null。之后父加载器开始尝试加载.class文件,加载成功就返回一个java.lang.Class,加载不成功就抛出一个ClassNotFoundException,给子加载器去加载。
最后如果要解析这个.class文件的话,就解析一下。