虚拟机加载机制之类加载器


在类的加载过程中的加载阶段,我们使用到了类加载器

一、在虚拟机中,类的唯一性是怎样确保的。

在虚拟机中,是通过加载类的类加载器来唯一确定一个类的。所以就算连个不同的类加载器加载的class文件一模一样,这两个类在虚拟机中也不是同一个类。那么用instanceof、equeals()方法、isAssignableFrom()、isInstance()方法也得不到想要的结果。

例:

package fengli;

import java.io.IOException;
import java.io.InputStream;

public class Main {
    public static void main(String[] args) throws Exception {
        ClassLoader cl = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                InputStream is = getClass().getResourceAsStream(fileName);
                if (is == null) {
                    return super.loadClass(name);
                }
                try {
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException(name);
                }
            }
        };

        Object obj1=cl.loadClass("fengli.Main").newInstance();
        Object obj2=new Main();
        System.out.println(obj1.getClass());//class fengli.Main
        System.out.println(obj2.getClass());//class fengli.Main
        System.out.println(obj1 instanceof fengli.Main);//false
        System.out.println(obj2 instanceof fengli.Main);//true
    }
}

运行结果:
在这里插入图片描述

二、双亲委派模型

从虚拟机角度,只存在两种不同的类加载器。

  1. 一种是启动类加载器(BootStrap ClassLoader),这个类加载器使用C++实现,是虚拟机的一部分。
  2. 另一个就是其他类加载器。都继承与java.lang.ClassLoader。这是有java语言实现的,独立于虚拟机外部。

然而从开发人员角度,有三种类加载器。至于这三类加载器,我在:类的加载机制具体说道它们的区别了。

  1. 启动类加载器
  2. 扩展类加载器
  3. 应用程序类加载器,也称为系统类加载器。

双亲委派的类加载器之间的层次图。他们不是以继承关系组织的,而是用组合关系来复用父类加载器。
在这里插入图片描述
双亲委派的加载过程是这样的:

当用一个类加载器区加载Class文件时,先让其父类加载器区加载这个Class文件,当其父类不能加载这个Class文件时(在搜索范围没有找到这个类),才让这个类区加载Class文件。

器源码如下:

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 {
                	//如果没有,就尝试使用父类加载器区加载这个类(name)
                    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
                }
				//如果父类加载器在搜索范围内没有对应的类,就调用自己的findClass方法进行加载。
                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;
        }
    }

使用双亲委派的好处是类随着它的类加载器一同具备了层次关系。因此Object类在双亲委派下,始终是同一个类。

三、破坏双亲委派模型

双亲委派并不是一种强制性的约束模型,而是java设计者推荐给开发者的一种类加载器实现方式。在历史上,双亲委派模型发生过三次大规模“被破坏”(并不具有贬义,指的是一种创新)情况。

第一次:添加了一个新的protected的finaClass().

第二次:是由于双亲委派的缺陷造成的,父类加载器不能调用子类加载器。于是使用线程上下文加载器区解决这个问题。

第三次:是由于用户对程序的动态性(代码热替换、模块热部署)的追求造成的。

猜你喜欢

转载自blog.csdn.net/wobushixiaobailian/article/details/83574673