Java类加载机制详解

一、类加载器

类加载器(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、类加载器在加载类在此以前会先递归的去品味选拔父加载器加载。 
Bootstrap,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 技术就选取了一种网状的协会,而非双亲委派机制。

 

免责声明:本小说和消息来源国际互连网,本网转载出于传递越来越多音信和上学之目标。如转发稿涉及版权等题材,请及时联系。大家会给予变更或删除相关小说,有限协助你的任务。

相关文章