类加载器:

java虚拟机设计团队把类加载阶段中的“通过一个类的全限定类名(如java.lang.String)来获取此类的二进制字节流”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

简单来说:类加载器负责把编译好的字节码文件加载到元空间中。(个人理解)

判断两个类是否相等:

两个类是否相等与类加载器和class文件 有关。

两个类来源于同一个class文件,但是加载他们的类加载器不一样,这两个类必然不相等。

类加载器的分类:

从jvm角度来看,只有两种不同的类加载器:

一种是启动类加载器(Bootstrap ClassLoader)也可以叫引导类加载器,这个类加载器由c++编写,是jvm的一部分;

另一种则是其他所有的类加载器,这些类加载器由java编写,都继承于抽象类java.lang.ClassLoader.

双亲委派模型:

java工具类使两个数据想加 java 两个类_类加载器

上图是上面所介绍的这几种类加载器的层次关系,称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。

双亲委派模型的工作过程:

如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到启动类加载器中,只有当父类加载器在它的搜索范围内没有找到所需的类时,子加载器才会自己尝试去加载。如果所有类加载器都无法加载,则程序抛出异常。

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类的对象,运行程序,运行结果如下:

java工具类使两个数据想加 java 两个类_类加载器_02

 可以发现: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类。

当然双亲委派模型也是可以打破的,具体做法可通过自定义类加载器来实现:具体做法此次不加以说明。

总结:蒟蒻第一次写博客,可能有诸多不对的地方,求各位大佬指点!