Java:类加载器(ClassLoader)

听上去很高端,其实一般自定义类加载器不需要用户去实现解析的过程,只要负责实现获取类对应的.class字节流部分就ok了,摘录深入理解Java虚拟机的一段话

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

实现类加载器需要继承ClassLoader这个抽象类,用户只要实现其中的findClass方法即可。在该类的javadoc中给出了这样一个示例:

      class NetworkClassLoader extends ClassLoader {
          String host;
          int port;
 
          public Class findClass(String name) {
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);
          }
 
          private byte[] loadClassData(String name) {
              // load the class data from the connection
          }
      }

即用户这个自定义的类加载器的类实现文件在网络的某个位置而不是本地的文件。defineClass是ClassLoader类中的一个函数,底层调用了本地方法用来做真正的类解析工作。用户定义的findClass方法会在已有加载器无法加载指定名称的类时被调用。具体在loadClass方法中:

    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;
        }
    }

我们继承的ClassLoader中含有一个parent ClassLoader的成员,如果它不为null,那么类先会让它去尝试着加载,当它找不到指定的类时再调用用户定义的findClass过程来加载。

Parents Delegation Model(双亲委派模型)

上述先让类加载器中存在parent类加载器,并且先让parent类加载器尝试类加载的过程涉及到类加载器的“双亲委派模型”。双亲其实就是一个祖先(parents对应的中文而已)即当前ClassLoader中的parent成员,委派就是先让parent加载器代理类加载过程,不行再用自己的类加载过程。按照深入理解JVM的说法有系统提供的三种类加载器它们的层次如下:

|———————————————|

|    Bootstrap ClassLoader       |

|———————————————|

                  |

|----------------------------------|

|   Extension ClassLoder        |

|----------------------------------|

                  |

|----------------------------------|

|   Application ClassLoder      |

|----------------------------------|

 

Bootstrap ClassLoader: 负责加载存放在JAVA_HOME/lib目录中的或者被-Xbootclasspath参数所指定的,并且是按名称被虚拟机识别的(如rt.jar,名字不符不会识别)

Extension ClassLoader: 负责加载JAVA_HOME/lib/ext目录中的或者被java.ext.dirs系统变量指定的路径中的所有类库

Application ClassLoader: 负责加载用户路径(classpath)上所指定的类库,一般情况下这个就是程序中默认的类加载器

 bootstrap classloader不能被直接获取,如下代码将输出null,代表Integer这个类是由bootstrap加载的,也可以从ClassLoader的loadClass判断过程看出如果parent==null就采用bootstrap去加载。

System.out.println(Integer.class.getClassLoader());

application classloader可以通过ClassLoader.getSystemClassLoader()静态方法获取,如下代码将返回true,klass是一个用户定义的类的class属性

System.out.println(ClassLoader.getSystemClassLoader() == klass.getClassLoader());

自己定义的类加载器默认情况下将ApplicationClassLoader作为parent类加载器。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。