虚拟机类加载机制--类加载器
准备阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到了Java虚拟机外部去实现,以便让应用程序自己决定如何如获取所需要的类。实现这个动作的代码模块称为“类加载器”
1.类与类加载器
每一个类加载器都有一个独立的类名称空间,由类加载器和类一起合作才能确定一个类在虚拟机中的唯一性。也就是说:比较两个类是否“相等”,即使他们来自同一个Class文件,在同一个虚拟机上被加载,如果加载它们的类加载器不同,那么这两个类就不相等。
这里的“相等”,包括类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法,以及使用instanceof关键字作对象所属关系判定等情况。
2.双亲委派模型
虚拟机角度来说,有两种类加载器:
一种是启动类加载器,使用C++语言实现。
另一种就是所有其他的类加载器,这些类加载器都由java语言实现,独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader。
按照开发人员的角度来看,绝大部分java程序都会使用到以下3种系统提供的类加载器:
启动类加载器:负责加载存放在%JAVA_HOME%\lib目录中的,或者通被-Xbootclasspath参数所指定的路径中的,并且被java虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库,即使放在指定路径中也不会被加载)类库到虚拟机的内存中,启动类加载器无法被java程序直接引用
扩展类加载器:由sun.misc.Launcher$ExtClassLoader实现,负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器:由sun.misc.Launcher$AppClassLoader实现,负责加载用户类路径classpath上所指定的类库,是类加载器ClassLoader中的getSystemClassLoader()方法的返回值,开发者可以直接使用应用程序类加载器,如果程序中没有自定义过类加载器,该加载器就是程序中默认的类加载器。
类加载器之间的这种层次关系,称为类加载器的双亲委派模型。这里类加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合关系来复用父加载器的代码。
//验证类加载器与类加载器间的父子关系 public static void main(String[] args) throws Exception{ //获取系统/应用类加载器 ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); System.out.println("系统/应用类加载器:" + appClassLoader); //获取系统/应用类加载器的父类加载器,得到扩展类加载器 ClassLoader extcClassLoader = appClassLoader.getParent(); System.out.println("扩展类加载器" + extcClassLoader); System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs")); //获取扩展类加载器的父加载器,但因根类加载器并不是用Java实现的所以不能获取 System.out.println("扩展类的父类加载器:" + extcClassLoader.getParent()); }
类加载器的双亲委派加载机制(重点):当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
使用双亲委派模型的好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系,例如类java.lang.Object,它存放在rt.java之中,无论哪一个类要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在程序的各种类加载器环境中都是一个类。
双亲委派模型的实现:
主要体现在ClassLoader的loadClass()方法中,思路很简单:先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载器加载失败,抛出ClassNotFoundException异常后,调用自己的findClass()方法进行加载。
1. 执行findLoadedClass(String)
去检测这个class是不是已经加载过了。
2. 执行父加载器的loadClass
方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
3. 如果向上委托父加载器没有加载成功,则通过findClass(String)
查找。
如果class在上面的步骤中找到了,参数resolve又是true的话,那么loadClass()
又会调用resolveClass(Class)
这个方法来生成最终的Class对象。 我们可以从源代码看出这个步骤。
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { 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. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
下面看一个简单的双亲委派模型代码实例验证:
public class ClassLoaderTest { public static void main(String[] args){ //输出ClassLoaderText的类加载器名称 System.out.println("ClassLoaderText类的加载器的名称:"+ClassLoaderTest.class.getClassLoader().getClass().getName()); System.out.println("System类的加载器的名称:"+System.class.getClassLoader()); System.out.println("List类的加载器的名称:"+List.class.getClassLoader()); ClassLoader cl = ClassLoaderTest.class.getClassLoader(); while(cl != null){ System.out.print(cl.getClass().getName()+"->"); cl = cl.getParent(); } System.out.println(cl); }
3.破坏双亲委派模型
双亲委派模型并不是一个强制性的约束模型,但是java中大部分的类加载器都遵循这个模型,但也有意外,目前为止,双亲委派模型中主要出现过三次较大规模的“被破坏”的情况