BootstrapJava类加载机制详解

同样、类加载器

看似加载器(ClassLoader),顾名思义,即加载类的物。在我们用一个近乎之前,JVM需要先拿此类的许节码文件(.class文件)从磁盘、网络或任何来源加载到内存中,并对准配节码进行辨析生成对应的Class对象,这便是近似加载器的功用。我们好采取类似加载器,实现类似的动态加载。

次、类的加载机制

当Java中,采用双亲委派机制来落实类似的加载。那什么是父母亲委派机制?在Java
Doc中发生这样平等段子描述:

The ClassLoader class uses a delegation model to search for classes
and resources. Each instance of ClassLoader has an associated parent
class loader. When requested to find a class or resource, a
ClassLoader instance will delegate the search for the class or
resource to its parent class loader before attempting to find the
class or resource itself. The virtual machine’s built-in class loader,
called the “bootstrap class loader”, does not itself have a parent but
may serve as the parent of a ClassLoader instance.

由以上描述着,我们得总结出如下四点: 
1、类的加载过程用委托模式实现 
2、每个 ClassLoader 都来一个父加载器。 
3、类加载器在加载类之前见面先递归的错过尝尝采取父加载器加载。 
4、虚拟机有一个内建的起步类加载器(Bootstrap ClassLoader),该加载器没有父加载器,但是得看作其它加载器的父加载器。 
Java
提供三种植类型的系类加载器。第一种是开行类加载器,由C++语言实现,属于JVM的等同有,其作用是加载
/lib 目录中的文件,并且该类加载器只加载特定称谓的文书(如
rt.jar),而休是欠目录下有的公文。另外两栽是 Java
语言本身实现的类似加载器,包括扩大类加载器(ExtClassLoader)和应用类加载器(AppClassLoader),扩展类加载器负责加载\lib\ext目录中还是体系变量
java.ext.dirs
所指定的目录中之公文。应用程序类加载器负责加载用户类路径中的文件。用户可一直下扩展类加载器或系统类加载器来加载自己的近乎,但是用户无法直接利用启动类加载器,除了这简单种植恍若加载器以外,用户也得起定义类加载器,加载流程如下图所示: 
Bootstrap 1
瞩目:这里父类加载器并无是经连续关系来落实之,而是利用组合实现的。 
我们可以通过平等段子先后来证明这个进程:

/**
 * Java学习交流QQ群:589809992 我们一起学Java!
 */
public class Test {
}

public class TestMain {
    public static void main(String[] args) {

        ClassLoader loader = Test.class.getClassLoader();
        while (loader!=null){
            System.out.println(loader);
            loader = loader.getParent();
        }
    }
}

方程序的运行结果如下所示:

Bootstrap 2

自打结果我们得望,默认情况下,用户从定义的切近使用 AppClassLoader
加载,AppClassLoader 的父加载器为 ExtClassLoader,但是 ExtClassLoader
的父加载器却显得为空,这是呀原因吧?究其故,启动类加载器属于 JVM
的同样部分,它不是由于 Java 语言实现的,在 Java
中无法直接引用,所以才回去空。但如是这么,该怎么落实 ExtClassLoader 与
启动类加载器之间上下委派机制?我们可参考一下源码:

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

起源码可以看来,ExtClassLoader 和 AppClassLoader都蝉联自 ClassLoader
类,ClassLoader 类中通过 loadClass
方法来实现上下委派机制。整个类的加载过程可分为如下三步:

1、查找对应之近乎是否都加载。 
2、若无加载,则判断当前看似加载器的父加载器是否也空,不呢空则委托为父类去加载,否则调用启动类加载器加载(findBootstrapClassOrNull
再望生会调用一个 native 方法)。 
3、若第二步加载失败,则调用当前好像加载器加载。

由此上面立段先后,可以挺清楚的相扩展类加载器与开行类加载器之间是安贯彻委托模式之。

今,我们重新作证另一个问题。我们以刚的Test类打成jar包,将那放于 \lib\ext
目录下,然后还运行方面的代码,结果如下:

Bootstrap 3

现行,该类就不再通过 AppClassLoader 来加载,而是通过 ExtClassLoader
来加载了。如果我们试图将jar包拷贝到\lib,尝试通过启动类加载器加载该类时,我们见面意识编译器无法辨识该类,因为启动类加载器除了指定目录外,还须是一定称谓的文书才能够加载。

老三、自定义类加载器

一般性情况下,我们都是一直利用系统类加载器。但是,有的时候,我们吧欲从定义类加载器。比如用是经网来传
Java
类的字节码,为保安全性,这些字节码经过了加密处理,这时系统类加载器就无法对那进展加载,这样尽管要打定义类加载器来贯彻。自定义类加载器一般都是累自
ClassLoader 类,从地方对 loadClass
方法来分析来拘禁,我们唯有需要运动康复中心重写
findClass 方法即可。下面我们经过一个演示来演示自定义类加载器的流水线:

package com.paddx.test.classloading;

import java.io.*;

/**
 * Created by liuxp on 16/3/12.
 */
public class MyClassLoader extends ClassLoader {

    private String root;

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
        String fileName = root + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
        try {
            InputStream ins = new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getRoot() {
        return root;
    }

    public void setRoot(String root) {
        this.root = root;
    }

    public static void main(String[] args)  {

        MyClassLoader classLoader = new MyClassLoader();
        classLoader.setRoot("/Users/liuxp/tmp");

        Class<?> testClass = null;
        try {
            testClass = classLoader.loadClass("com.paddx.test.classloading.Test");
            Object object = testClass.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

运作方面的主次,输出结果如下:

Bootstrap 4

由定义类加载器的主干在针对配节码文件之获取,如果是加密的配节码则需以此类中对文本进行解密。由于此处只是演示,我无对class文件进行加密,因此没有解密的长河。这里发出几乎触及要留意:

1、这里传递的公文称急需是相仿的全限定性名称,即com.paddx.test.classloading.Test格式的,因为
defineClass 方法是按部就班这种格式进行拍卖的。 
2、最好不要再写loadClass方法,因为这么好破坏双亲委托模式。 
3、这仿佛 Test 类本身可以给 AppClassLoader 类加载,因此我们无能够将
com/paddx/test/classloading/Test.class
放在类路径下。否则,由于老人委托机制的留存,会一直造成该类由
AppClassLoader 加载,而不见面通过我们由定义类加载器来加载。

四、总结

老人委派机制能好好地解决类加载的统一性问题。对一个 Class
对象的话,如果类似加载器不同,即便是和一个配节码文件,生成的 Class
对象也是殊的。也就是说,类加载器相当给 Class
对象的一个命名空间。双亲委派机制虽然保证了基类都是因为同之类似加载器加载,这样就是避免了和一个配节码文件被频繁加载生成不同之
Class 对象的题材。但父母委派机制才是Java
规范所推荐的同样栽实现方式,它并无是强制性的要求。近年来,很多暖部署的技巧还曾无照这无异于条条框框,如
OSGi 技术就是使用了相同种植网状的构造,而不父母委派机制。

 

免责声明:本文章和信来自国际互联网,本网转载出于传递更多信息及读书之目的。如转载稿涉及版权等题材,请立即联系。我们会给变更或去相关文章,保证你的权利。

相关文章