类加载器
通过类的全限定名来获取描述此类的二进制字节流,把类加载阶段中的这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取需要的类,实现这个动作的代码模块称为“类加载器”。
public abstract class ClassLoader {
// 父加载器
private final ClassLoader parent;
// 加载类的核心方法之一。如果没找到类则抛出ClassNotFoundException
protected Class loadClass(Stringname, boolean resolve)throws ClassNotFoundException{
// synchronized意味着:
// 1. 同一时间只允许一个线程加载名字为name的类
synchronized (getClassLoadingLock(name)) {
// First, check if the class has alreadybeen loaded
// 2. 在加载之前先检查,是否已经加载过该类
Class c = findLoadedClass(name);
// 3. c==null代表只有没有被加载过的类才会进行加载
if (c == null) {
long t0 = System.nanoTime();
try {
// 双亲委派
// 父加载器能加载的绝不给子类加载
if (parent != null) {
// 如果父加载器不为null,则把类加载的工作交给父加载器
c = parent.loadClass(name, false);
} else {
// 如果父加载器为null,则把类加载的工作交给BootstrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if classnot found
// from the non-null parent class loader
}
// 如果父加载器不能加载,子加载器去尝试加载
if (c == null) {
// If still not found, then invokefindClass 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;
}
}
}
ClassLoader.loadClass核心代码总结:
同一时间只允许一个线程加载名字为name的类。
在加载之前先检查,是否已经加载过该类。只有没有加载过的才允许加载
双亲委派:父加载器能加载的绝不给子类加载
父加载器加载不到类的情况,交给子加载器去加载(即:findClass)
虚拟机类加载器
从Java虚拟机角度来讲,只存在两种不同的类加载器:一种是启动类加载器(BootstrapClassLoader),由C++语言实现,是虚拟机自身的一部分;另一种是所有其他的类加载器,由Java语言实现,独立于虚拟机外部,全部继承抽象类java.lang.ClassLoader。
从开发人员角度来看,类加载器大致分一下三种:
启动类加载器(BootstrapClassLoader):负责将存放在\lib目录中的,或被-Xbootclasspath参数指定的路径中的,并且是虚拟机识别的类库加载到虚拟机中。如,rt.jar。名字不符合即使放在目录中也不被加载。如果需要把加载请求委派给引导类加载器,直接使用null代替即可。
扩展类加载器(ExtensionClassLoader):由sum.misc.Launcher$ExtClassLoader实现,负责加载<Java_Home>\lib\ext目录中的,或者被java.ext.dir系统变量所指定的路径中的所有类库。开发者可以直接使用扩展类加载器。
应用程序类加载器(ApplicationClassLoader):由sun.misc.Launcher$App-ClassLoader实现。是ClassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器。负责加载用户路径(ClassPath)上所指定的类库,如果应用程序中没有自定义过自己的类加载器,这个就是默认的加载器,开发人员可以直接使用这个类加载器。
自定义类加载器
自定义加载器有何用途:
》加密
Java代码很容易被反编译,如果你需要对你的代码进行加密以防止反编译,则可以将编译后的代码用加密算法加密。但加密后的类就不能再使用Java默认的类加载器进行加载,这时候就需要自定义类加载器。
》非标准的来源加载代码
字节码是放在数据库或者网络位置,需要自定义类加载器
双亲委派
什么是双亲委派
如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,因此所有的类加载请求最终都会传送到顶端的启动类加载器;
只有当父加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子加载器,子加载器会尝试去自己加载。总结:父加载器能加载的绝不给子加载器加载,只有父加载器找不到所需的类才让子加载器尝试加载。
如何打断默认双亲委派机制
如果想打破双亲委派模型,只需要重写loadClass方法即可。
为什么使用双亲委派
对任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。
判断两个类是否"相等",必须是在这两个类被同一个类加载器加载的前提下。
基于双亲委派模型设计,那么Java中基础类,如Object类重复多次的问题就不会存在了,因为经过层层传递,加载请求最终都会被BootstrapClassLoader所响应。加载的Object类也会只有一个,否则如果用户自己编写了一个java.lang.Object类,并把它放到了ClassPath中,会出现很多个Object类,这样Java类型体系中最最基础的行为都无法保证,应用程序也将一片混乱。
网友评论