类加载器
首先我们要知道 .class 文件存储是类的基本信息。
类加载
每个 .java 文件经过编译就会产生一个 .class 文件,当我们需要某个类,虚拟机就会将它的 .class 文件加载到虚拟机的内存,并创建相应的 class 对象,这就是类加载的过程。
类加载器的目标
类加载器就是实现这个类加载的过程。
那么类加载器是如何区分类的呢?主要是因为 SUN 公司规定了每个 class 文件的开头必须以 0xCAFEBABE(也称为魔数) 开头,这使得类加载器可以区分出来。
类加载器
虚拟机里提供了三种类加载器,分别是启动(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)。
加载器 | 功能 |
Bootstrap ClassLoader 启动类加载器 | 也称引导类加载器或者根加载器,主要加载 jar/lib/rt.jar 文件。 |
Ext ClassLoader 扩展类加载器 | 主要加载核心扩展类,即 JAVA_HOME/jre/lib/ext 下的jar 文件。 |
App ClassLoader 系统类加载器 | 主要加载用户自定义的类,classPath 下的 jar 文件和目录。 |
类加载的方式——双亲委派方式
其加载原理是如果子类收到了一个类加载的请求,子类不会去处理这个请求,而是把请求交给父类,如果父类还有父类,就继续传递请求给父类,直到到达根加载器。如果根加载器完成加载任务就成功返回,否叫交给子类去处理。这是一个递归的过程。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
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) {
}
//父类没加载成功
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
这个代码实现了类加载的双亲委派机制。
其中有几个比较重要的方法 findClass 方法和 defineClass 方法。
findClass:根据类的包路径找到 class 文件。
defineClass :负责从 class 字节码中加载 Class 对象,然后 Class 对象通过反射机制生成对象。
当我们去写自己的自定义类加载器时,不用每次都重写 loadClass 方法,这样会破坏它的双亲委派机制,只需要重写 findClass方法就好了。