• Java类加载机制及自定义加载器


      一:ClassLoader类加载器,主要的作用是将class文件加载到jvm虚拟机中。jvm启动的时候,并不是一次性加载所有的类,而是根据需要动态去加载类,主要分为隐式加载和显示加载。

      隐式加载:程序代码中不通过调用ClassLoader来加载需要的类,而是通过JVM类自动加载需要的类到内存中。例如,当我们在类中继承或者引用某个类的时候,JVM在解析当前这个类的时,发现引用的类不在内存中,那么就会自动将这些类加载到内存中。

      显示加载:代码中通过Class.forName(),this.getClass.getClassLoader.LoadClass(),自定义类加载器中的findClass()方法等。

      二:jvm自带的加载器

      (1)BootStrap ClassLoader:主要加载%JRE_HOME%lib下的rt.jar、resources.jar、charsets.jar和class等。可以通System.getProperty("sun.boot.class.path")

         查看加载的路径,如下:

    package test;
    
    public class TestGC {
        
        public static void main(String []args){
    
            System.out.println(System.getProperty("sun.boot.class.path"));
            
        }
    }

        显示结果如下:

    D:Program FilesJavajdk1.7.0_45jrelib
    esources.jar;D:Program FilesJavajdk1.7.0_45jrelib
    t.jar;D:Program FilesJavajdk1.7.0_45jrelibsunrsasign.jar;D:Program FilesJavajdk1.7.0_45jrelibjsse.jar;D:Program FilesJavajdk1.7.0_45jrelibjce.jar;D:Program FilesJavajdk1.7.0_45jrelibcharsets.jar;D:Program FilesJavajdk1.7.0_45jrelibjfr.jar;D:Program FilesJavajdk1.7.0_45jreclasses

      (2)Extention ClassLoader:主要加载目录%JRE_HOME%libext目录下的jar包和class文件。也可以通过System.out.println(System.getProperty("java.ext.dirs"))查看加载类文件的路径。

      (3)AppClassLoader:主要加载当前应用下的classpath路径下的类。之前我们在环境变量中配置的classpath就是指定AppClassLoader的类加载路径。

      三:类加载器的继承关系

      先看一下这三个类加载器之间的继承关系,如下图:

      

      ExtClassLoader,AppClassLoder继承URLClassLoader,而URLClassLoader继承ClassLoader,BoopStrap ClassLoder不在上图中,因为它是由C/C++编写的,它本身是虚拟机的一部分,并不是一个java类。jvm加载的顺序:BoopStrap ClassLoder-〉ExtClassLoader->AppClassLoder,下面看一段源码:

    public class Launcher {
        private static Launcher launcher = new Launcher();
        private static String bootClassPath =
            System.getProperty("sun.boot.class.path");
    
        public static Launcher getLauncher() {
            return launcher;
        }
    
        private ClassLoader loader;
    
        public Launcher() {
            // Create the extension class loader
            ClassLoader extcl;
            try {
                extcl = ExtClassLoader.getExtClassLoader();
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create extension class loader", e);
            }
    
            // Now create the class loader to use to launch the application
            try {
                loader = AppClassLoader.getAppClassLoader(extcl);
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create application class loader", e);
            }
    
            Thread.currentThread().setContextClassLoader(loader);
        }
    
        /*
         * Returns the class loader used to launch the main application.
         */
        public ClassLoader getClassLoader() {
            return loader;
        }
        /*
         * The class loader used for loading installed extensions.
         */
        static class ExtClassLoader extends URLClassLoader {}
    
    /**
         * The class loader used for loading from java.class.path.
         * runs in a restricted security context.
         */
        static class AppClassLoader extends URLClassLoader {}

       从源码中我们看到:(1)Launcher初始化的时候创建了ExtClassLoader以及AppClassLoader,并将ExtClassLoader实例传入到AppClassLoader中。

       (2)虽然上一段源码中没见到创建BoopStrap ClassLoader,但是程序一开始就执行了System.getProperty("sun.boot.class.path")。

      四:类加载器之间的父子关系

        AppClassLoader的父加载器为ExtClassLoader,ExtClassLoader的父加载器为null,BoopStrap ClassLoader为顶级加载器。

      下面一个小例子就可以证明,如下:新建一个Test类,可以通过getParent()方法获取上一层父机载器,执行如下代码:

    package test;
    
    public class TestGC {
        
        public static void main(String []args){
            
            System.out.println(Test.class.getClassLoader().toString());
            
            System.out.println(Test.class.getClassLoader().getParent().toString());
            
            System.out.println(Test.class.getClassLoader().getParent().getParent().toString());
        }
    }

      输出结果如下:

      

      五:类加载机制-双亲委托机制

      例如:当jvm要加载Test.class的时候,

      (1)首先会到自定义加载器中查找,看是否已经加载过,如果已经加载过,则返回字节码。

      (2)如果自定义加载器没有加载过,则询问上一层加载器(即AppClassLoader)是否已经加载过Test.class。

      (3)如果没有加载过,则询问上一层加载器(ExtClassLoader)是否已经加载过。

      (4)如果没有加载过,则继续询问上一层加载(BoopStrap ClassLoader)是否已经加载过。

      (5)如果BoopStrap ClassLoader依然没有加载过,则到自己指定类加载路径下("sun.boot.class.path")查看是否有Test.class字节码,有则返回,没有通

    知下一层加载器ExtClassLoader到自己指定的类加载路径下(java.ext.dirs)查看。

      (6)依次类推,最后到自定义类加载器指定的路径还没有找到Test.class字节码,则抛出异常ClassNotFoundException。如下图:

      

      六:类加载过程的几个方法

      (1)loadClass     (2)findLoadedClass     (3)findClass     

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 首先,检查是否已经加载过
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            //父加载器不为空,调用父加载器的loadClass
                            c = parent.loadClass(name, false);
                        } else {
                            //父加载器为空则,调用Bootstrap Classloader
                            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();
                        //父加载器没有找到,则调用findclass
                        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()
                    resolveClass(c);
                }
                return c;
            }
        }

      七:自定义类加载器步骤

      (1)继承ClassLoader    (2)重写findClass()方法   (3)调用defineClass()方法

      下面写一个自定义类加载器:指定类加载路径在D盘下的lib文件夹下。

      (1)新建一个Test.class类,代码如下:

    package com.test;
    
    public class Test {
    
        public void say(){
    System.out.println(
    "Hello MyClassLoader"); } }

      (2)cmd控制台执行javac Test.java,将生成的Test.class文件放到D盘lib文件夹->com文件夹->test文件夹下。

      (3)自定义类加载器,代码如下:

    package test;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    
    public class MyClassLoader extends ClassLoader{
    
        private String classpath;
        
        public MyClassLoader(String classpath) {
            
            this.classpath = classpath;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte [] classDate=getDate(name);
                
                if(classDate==null){}
                
                else{
                    //defineClass方法将字节码转化为类
                    return defineClass(name,classDate,0,classDate.length);
                }
                
            } catch (IOException e) {
                
                e.printStackTrace();
            }
            
            return super.findClass(name);
        }
        //返回类的字节码
        private byte[] getDate(String className) throws IOException{
            InputStream in = null;
            ByteArrayOutputStream out = null;
            String path=classpath + File.separatorChar +
                        className.replace('.',File.separatorChar)+".class";
            try {
                in=new FileInputStream(path);
                out=new ByteArrayOutputStream();
                byte[] buffer=new byte[2048];
                int len=0;
                while((len=in.read(buffer))!=-1){
                    out.write(buffer,0,len);
                }
                return out.toByteArray();
            } 
            catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            finally{
                in.close();
                out.close();
            }
            return null;
        }
    }

      测试代码如下:

    package test;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class TestMyClassLoader {
    
        public static void main(String []args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{
            //自定义类加载器的加载路径
            MyClassLoader myClassLoader=new MyClassLoader("D:\lib");
            //包名+类名
            Class c=myClassLoader.loadClass("com.test.Test");
            
            if(c!=null){
                Object obj=c.newInstance();
                Method method=c.getMethod("say", null);
                method.invoke(obj, null);
                System.out.println(c.getClassLoader().toString());
            }
        }
    }

      输出结果如下:

      

      自定义类加载器的作用:jvm自带的三个加载器只能加载指定路径下的类字节码。如果某个情况下,我们需要加载应用程序之外的类文件呢?比如本地D盘下的,或者去加载网络上的某个类文件,这种情况就可以使用自定义加载器了。

       参考网址:http://blog.csdn.net/briblue/article/details/54973413

  • 相关阅读:
    30岁前不必在乎的28件事
    开发工作的枯燥与快乐
    Ajax技术的初步认识二(转)
    算法实习一
    判断程序是否运行的代码 (有点单例模式的味道)
    SQL2008+SERVER2008的解决方案
    天秤座的爱情(转)
    VB sytem32\ieframe.dll找不到的解决方案
    回忆有感《清明雨上》
    Ajax技术的初步认识一(转)
  • 原文地址:https://www.cnblogs.com/gdpuzxs/p/7044963.html
Copyright © 2020-2023  润新知