类加载器:
java虚拟机设计团队把类加载阶段中的“通过一个类的全限定类名(如java.lang.String)来获取此类的二进制字节流”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
简单来说:类加载器负责把编译好的字节码文件加载到元空间中。(个人理解)
判断两个类是否相等:
两个类是否相等与类加载器和class文件 有关。
两个类来源于同一个class文件,但是加载他们的类加载器不一样,这两个类必然不相等。
类加载器的分类:
从jvm角度来看,只有两种不同的类加载器:
一种是启动类加载器(Bootstrap ClassLoader)也可以叫引导类加载器,这个类加载器由c++编写,是jvm的一部分;
另一种则是其他所有的类加载器,这些类加载器由java编写,都继承于抽象类java.lang.ClassLoader.
双亲委派模型:
上图是上面所介绍的这几种类加载器的层次关系,称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。
双亲委派模型的工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到启动类加载器中,只有当父类加载器在它的搜索范围内没有找到所需的类时,子加载器才会自己尝试去加载。如果所有类加载器都无法加载,则程序抛出异常。
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;
}
}
说明:class文件加载的流程,来验证上述的工作流程的说法。
双亲委派模型的好处:
避免出现多个全限定名称一样的类,如:用户自己编写了一个java.lang.String的类并放在classpath中,那么系统中会出现多个不同的String类。除此之外也可以保证应用程序的安全性,毕竟class文件可能来源于网络。
测试:
package java.lang;
public class String {
static {
System.out.println("是否被加载");
}
}
说明:自定义java.lang.String类
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
String s = new String(); // 运行无结果
}
}
运行结果:“是否被加载”没有打印,加载的是jdk自带的String类
总结:自定义全限定名一样的类不会被加载
那类名不一样是否能被加载呢?
测试如下:自定义java.lang.String1类,然后在测试类中创建String1类的对象,运行程序,运行结果如下:
可以发现:String1类被认为是不安全的类。
以上几种加载器的介绍:
启动类加载器:负责加载存放在%JAVA_HOME%\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且能够被jvm识别的(按照文件名识别,如rt.jar)类库加载到虚拟机的内存中。
启动类加载器由于是c++编写,无法通过java程序直接引用。
扩展类加载器(ExtClassLoader):负责加载%JAVA_HOMT%\lib\ext目录中,或者被java.ext.dirs系统变量所指定路径中的所有类库。用户可以把第三方jar包放于该目录中来扩展java se的功能。当然,用户也可以直接在程序中指定扩展类加载器加载class文件。
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
LocaleNames localeNames = new LocaleNames();
ClassLoader classLoader = localeNames.getClass().getClassLoader();
// 输出加载LocaleNames类的加载器: sun.misc.Launcher$ExtClassLoader@677327b6
System.out.println("加载LocaleNames类的加载器:\t"+classLoader);
Class<?> aClass = classLoader.loadClass("java.lang.String");
System.out.println(aClass.getClassLoader());
// null -- 表示String的class文件由启动类加载器加载
}
}
由此可见:即使指定特定的类加载器加载class文件,双亲委派机制依然有效。
应用程序类加载器:主要负责加载用户类路径上的所有类库,用户自定义的类一般由此类加载加载器加载。
自定义类加载器:该加载器主要实现类的隔离,重载功等能。自定义类加载器的实现需要继承java.lang.ClassLoader类。