classLoader

452 阅读3分钟

简介:

classloader是class类的引导者,jvm对于Class类是需要时才加载,加载有两种方法是:

  1. 显示的:Class.forname() or loaderClass.load()
  2. 隐式加载:通过引用new一个类。自动加载Class类,并生成对象。

Class的加载模式

Class类加载模式为代理模式,

jvm在启动的时候生成3个classlOader;

  1. bootClassloader主要负载加载的jre中的核心库C:\Java\jdk1.8.0_60\jre\lib文件下main的jar包 与 C:\Java\jdk1.8.0_60\jre\classes下面的class类
  2. extclassloader外部类加载器,主要加载JAVA_HOME/jre/lib/ext/文件下的jar包
  3. 系统加载器,加载环境变量Classpath的文件路径 加载是有父classloader的,系统加载器的父加载器是外部加载,外部加载器的父是boot加载器。需要加载Class类的时候,首先让父加载器进行类的加载,如果父类不能加载,自己再加载。这样做的好处,是保护了核心Class类,我们调用核心类的时候总是在调用的jre的jar包。 加载过程如下: loadClass()
  4. 首先检查类是否加载过,如加载过返回。
  5. 没有加载过,调用父类的findClass(),如加载成功直接到4
  6. 如返回null,调用findClass()进行加载
  7. 根据2 3的结果将字节转换成class类------这一步进行了java名字注册。
  8. 根据flag的标志位,决定是否经resolveClass(),resolveClass就是对类进行检查,加载类对象的静态部分。

类加载函数

加载的过程如下:

 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 {    //下面几行对父加载器进行find,如果父加载器是null,说明是boot加载器
                    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) {//Classloader本身进行加载
                    // 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) {//进行link,检查。生成静态对象。
                resolveClass(c);
            }
            return c;
        }
    }

类的加载过程示意图

  1. 加载是defineClass()实现,
  2. 连接部分是resolveClass实现,在使用之前必须进行resolve

定义自己的类加载器

好处:可以对类加载器进行一些处理,例如是加密的类,在使用的时候首先进行解密。从类加载函数中知道,要进行类加载器需要重新findClass()函数,框架如下:

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

具体实现:只加载固定文件中的两个类。Class1 Classone。注意FileUtils.readFileToByteArray是apache的commens-io包的类。


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        if(name.equalsIgnoreCase("Classone")){
            return  load(1,name);
        }else if((name.equalsIgnoreCase("class2"))){
            return load(2,name);
        }else
            throw new ClassNotFoundException(name);

    }

    private Class<?> load(int i,String name) {
        if(i==1 | i==2){
            try {
                byte[] bytes1= FileUtils.readFileToByteArray(new File("D:\\java_temp\\jsp\\out\\production\\day288\\"+name+".class"));
                Class<?> classtype= defineClass(name, bytes1, 0, bytes1.length);
                resolveClass(classtype);
                return classtype;
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }

        }
        return null;

    }

web项目的加载顺序

注意web项目中有两个类加载器:tomcat类加载器,与项目加载器。tomcat加载是项目加载的父类。这两个加载是先寻找自身的类,再让父类加载(不包含核心类)。

  1. 项目加载器:先寻找WEB-INF下的lib与class文件,如果没有让tomcat加载器加载
  2. tomcat加载先加载lib文件下的类,如果没有交给系统加载器。

其他

不同类加载器加载相同的Class文件,Class类是不相同的。可以用对象的静态语句验证,Class会加载两次。

Class文件内部有类名字信息,所以在defineClass()中name变量要么为空,要么为类名字(与Class文件名字一致),不能是其他名字,如果是其他名字,会报error,找不到对象的类。

引用

  1. segmentfault.com/a/119000000… Java ClassLoader 工作机制
  2. 源码与java库文件文档 3