JAVA类加载机器classLoader

一、Java语言系统自带有三个类加载器 

1、 BootstrapClassLoader 最顶层的加载类,主要加载核心类库。BootstrapClassLoader是由C/C++编写的,它本身是虚拟机的一部分。 Object.class.getClassLoader()的返回值为null,因为Object类由Bootstrap ClassLoader加载,且Bootstrap ClassLoader不是一个java类,所以没有Bootstrap ClassLoader的java实例化对象,因此返回值为null。

2、ExtentionClassLoader 扩展的类加载器。

3、 AppclassLoader也称为SystemAppClass 加载当前应用的classpath的所有类。

二、三个类加载器是分别加载哪些类

类加载器都是有加载范围的,Launcher类中确定了他们的加载范围。

Launcher类(经过适当的改动)

public class Launcher {

    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);

    }

    static class ExtClassLoader extends URLClassLoader {
        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            final File[] var0 = getExtDirs();

            try {
                return (Launcher.ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Launcher.ExtClassLoader run() throws IOException {
                        int var1 = var0.length;

                        for (int var2 = 0; var2 < var1; ++var2) {
                            MetaIndex.registerDirectory(var0[var2]);
                        }

                        return new Launcher.ExtClassLoader(var0);
                    }
                });
            } catch (PrivilegedActionException var2) {
                throw (IOException) var2.getException();
            }
        }


        private static File[] getExtDirs() {
            String var0 = System.getProperty("java.ext.dirs");
            File[] var1;
            if (var0 != null) {
                StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
                int var3 = var2.countTokens();
                var1 = new File[var3];

                for (int var4 = 0; var4 < var3; ++var4) {
                    var1[var4] = new File(var2.nextToken());
                }
            } else {
                var1 = new File[0];
            }

            return var1;
        }
    }


    static class AppClassLoader extends URLClassLoader {
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }
    }


    private static class BootClassPathHolder {
        static final URLClassPath bcp;

        private BootClassPathHolder() {
        }

        static {
            URL[] var0;
            if (Launcher.bootClassPath != null) {
                var0 = (URL[]) AccessController.doPrivileged(new PrivilegedAction() {
                    public URL[] run() {
                        File[] var1 = Launcher.getClassPath(Launcher.bootClassPath);
                        int var2 = var1.length;
                        HashSet var3 = new HashSet();

                        for (int var4 = 0; var4 < var2; ++var4) {
                            File var5 = var1[var4];
                            if (!var5.isDirectory()) {
                                var5 = var5.getParentFile();
                            }

                            if (var5 != null && var3.add(var5)) {
                                MetaIndex.registerDirectory(var5);
                            }
                        }

                        return Launcher.pathToURLs(var1);
                    }
                });
            } else {
                var0 = new URL[0];
            }

            bcp = new URLClassPath(var0, Launcher.factory, (AccessControlContext) null);
            bcp.initLookupCache((ClassLoader) null);
        }
    }
    
}


1、BootstrapClassLoader虽然不是Java写的类加载器,但是它也是要去加载类的,通过BootClassPathHolder类可以发现其加载范围是由System.getProperty("sun.boot.class.path")决定。

2、ExtClassLoader加载范围是由System.getProperty("java.ext.dirs")决定。

3、AppClassLoader的创建过程中传入了ExtClassLoader,这里ExtClassLoader称为AppClassLoader的父加载器,ExtClassLoader的父加载器为null,BootstrapClassLoader这里可以简单粗暴的认为是ExtClassLoader的父加载器,因为BootstrapClassLoader的行为就像是ExtClassLoader的父加载器,如待会要说到的类的加载委托。AppClassLoader加载范围是由System.getProperty("java.class.path")决定。

 System.getProperty()中返回了什么数据?

示例:

    public static void main(String args[]) {

        String[] s = System.getProperty("sun.boot.class.path").split(";");
        System.out.println("Bootstrap ClassLoader 加载的类");
        System.out.println("  sun.boot.class.path:");
        Arrays.stream(s).forEach((i)->System.out.println("    "+i));

        s = System.getProperty("java.ext.dirs").split(";");
        System.out.println("ExtClassLoader 加载的类");
        System.out.println("  java.ext.dirs:");
        Arrays.stream(s).forEach((i)->System.out.println("    "+i));

        s = System.getProperty("java.class.path").split(";");
        System.out.println("AppClassLoader 加载的类");
        System.out.println("  java.class.path:");
        Arrays.stream(s).forEach((i)->System.out.println("    "+i));

    }

结果:

这就是三个类加载器默认的加载范围。

我电脑环境变量配置:

JAVA_HOME:C:\ProgramFiles\Java\jdk1.8.0_161

PATH:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

CLASSPATH:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar   注:需要注意的是前面的“.;”,“.;”代表当前命令行工作目录。

System.getProperty("java.class.path")在默认情况下获取的是CLASSPATH中的数据。

如果环境变量中CLASSPATH中的“.;”不写会出现什么情况?当类加载器去加载Test类的时候,没有一个类加载器可以找到该类,因此会出现:

三、如何修改类加载器的加载范围

类加载器都有默认的加载范围的,在程序开发过程中难免要去修改。

1、BootstrapClassLoader可以通过启动jvm时指定-Xbootclasspath来改变Bootstrap ClassLoader的加载目录。-Xbootclasspath: 完全取代基本核心的Javaclass 搜索路径,不常用,否则要重新写所有Java 核心class。-Xbootclasspath/a: 后缀在核心class搜索路径后面,常用!!。-Xbootclasspath/p: 前缀在核心class搜索路径前面。不常用,避免引起不必要的冲突。

2、Extention ClassLoader 扩展的类加载器,默认加载%JRE_HOME%\lib\ext目录下的jar包和class文件。可以通过-Djava.ext.dirs修改默认目录。

3、 App classLoader也称为SystemAppClass 加载当前应用的classpath的所有类。可以通过-classpath修改默认目录。

示例:

Person类的全称应是包含包名package的,即:www.com.Person。java中包名的体现是通过文件夹的嵌套的形式实现的
package www.com;
public class Person {

}

测试类

public class Test {
    public static void main(String args[]) throws Exception {
        System.out.println("www.com.Person ClassLoader = " + Class.forName("www.com.Person").getClassLoader());

        System.out.println("oracle.jdbc.driver.OracleDriver ClassLoader = " + Class.forName("oracle.jdbc.driver.OracleDriver").getClassLoader());

    }
}


在c盘建立文件(jodbc14.jar为oracle的驱动包


实例1:修改 App classLoader的加载范围   包含 c:/abc/calsspath中的ojdbc|.jar和Person.class

java -classpath ".;C:\Program Files\Java\jdk1.8.0_161\lib;C:\Program Files\Java\jdk1.8.0_161\lib\tools.jar;C:\abc\classpath\ojdbc14.jar;C:\abc\classpath"    Test

结果:


实例2:修改 Extention ClassLoader的加载范围   包含 c:/abc/ext中的ojdbc|.jar和Person.class


java  -D"java.ext.dirs=C:\Program Files\Java\jre1.8.0_161\lib\ext;C:\Windows\Sun\Java\lib\ext;C:\abc\ext"   Test

结果:



实例3:修改 App classLoader的加载范围   包含 c:/abc/boot中的ojdbc|.jar和Person.class

java  -Xbootclasspath/a:"C:\abc\boot\ojdbc14.jar;C:\abc\boot"   Test

结果:



四、类加载器的委托模式

三个类加载器的加载范围都是可以修改的,如果三个类加载器的加载范围重叠了,重叠区的类是由哪个类加载器加载呢?即使区域不重叠,如果一类在多个录入都出现了,这些目录被不同的类加载器读取,该类由哪个加载器加载呢?

一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class 自己的缓存中是否已经存在,如果存在就直接返回,如果不存在它并不是自己进行查找,而是交给父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrapclassloader的缓存中存在,直接返回,如果不存在,尝试从自己的加载目录中加载,如果成功加载,直接返回,如失败则一级一级返回。这种机制就叫做双亲委托。换句话说:如果一个类可以被多个类加载器扫描到,最终是由最顶层的父加载器加载。

上面已经详细介绍了加载过程,但具体为什么是这样加载。

ClassLoader的继承关系:


在ClassLoader类中:

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;
        }
    }
在loaderClass方法中:

1. 执行findLoadedClass(String)去检测这个class是不是已经加载过了。 

2. 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。 

3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。

示例:

java  -Xbootclasspath/a:"C:\abc\boot\ojdbc14.jar;C:\abc\boot"   -D"java.ext.dirs=C:\Program Files\Java\jre1.8.0_161\lib\ext;C:\Windows\Sun\Java\lib\ext;C:\abc\ext"   -classpath  ".;C:\Program Files\Java\jdk1.8.0_161\lib;C:\Program Files\Java\jdk1.8.0_161\lib\tools.jar;C:\abc\classpath\ojdbc14.jar;C:\abc\classpath"  Test
结果:



五、自定义ClassLoader

不知道大家有没有发现,不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果在某种情况下,我们需要动态加载一些东西呢?比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,这样可以吗?
如果要这样做的话,需要我们自定义一个classloader。
自定义步骤
编写一个类继承自ClassLoader抽象类。
复写它的findClass()方法。
在findClass()方法中调用defineClass()(这个方法在编写自定义classloader的时候非常重要,它能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常)。
注意点:
一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。上面说的是,如果自定义一个ClassLoader,默认的parent父加载器是AppClassLoader,因为这样就能够保证它能访问系统内置加载器加载成功的class文件。
假设我们需要一个自定义的classloader,默认加载路径为D:\lib下的jar包和资源。
我们写编写一个测试用的类文件Test.java。
Test.java
public class Test {

    public void say(){
        System.out.println("Say Hello");
    }

}

然后将它编译过年class文件Test.class放到D:\lib这个路径下。

我们编写DiskClassLoader的代码
public class DiskClassLoader extends ClassLoader {

    private String mLibPath;

    public DiskClassLoader(String path) {
        // TODO Auto-generated constructor stub
        mLibPath = path;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // TODO Auto-generated method stub

        String fileName = getFileName(name);

        File file = new File(mLibPath,fileName);

        try {
            FileInputStream is = new FileInputStream(file);

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len = 0;
            try {
                while ((len = is.read()) != -1) {
                    bos.write(len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            byte[] data = bos.toByteArray();
            is.close();
            bos.close();

            return defineClass(name,data,0,data.length);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return super.findClass(name);
    }

    //获取要加载 的class文件名
    private String getFileName(String name) {
        // TODO Auto-generated method stub
        int index = name.lastIndexOf('.');
        if(index == -1){ 
            return name+".class";
        }else{
            return name.substring(index+1)+".class";
        }
    }

}

测试:

public class ClassLoaderTest {

    public static void main(String[] args) {

        //创建自定义classloader对象。
        DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
        try {
            //加载class文件
            Class c = diskLoader.loadClass(Test");
            if(c != null){
                try {
                    Object obj = c.newInstance();
                    Method method = c.getDeclaredMethod("say",null);
                    //通过反射调用Test类的say方法
                    method.invoke(obj, null);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}

六、通过类加载器读取资源文件

程序中在读取资源文件时,一般通过类加载器获取当前路径,再加上文的相对路径。

类加载器的路径是由什么决定的?从getResource方法的中可以看到,在资源的获取上同样才取了委托的方式。如果parent不为null调用parent的getResource方法,如果parent为null调用getBootstrapResource方法;如果parent中无法找资源,就调用自己的findResoruce方法。

    public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }


E:\aa夺下新建一个www.com.Test类。包名是通过文件夹体现的。

package www.com;

import java.util.Arrays;

public class Test {

    public static void main(String args[]) throws Exception {

        String[] s = System.getProperty("sun.boot.class.path").split(";");
        System.out.println("Bootstrap ClassLoader 加载的类");
        System.out.println("  sun.boot.class.path:");
        Arrays.stream(s).forEach((i) -> System.out.println("    " + i));

        s = System.getProperty("java.ext.dirs").split(";");
        System.out.println("ExtClassLoader 加载的类");
        System.out.println("  java.ext.dirs:");
        Arrays.stream(s).forEach((i) -> System.out.println("    " + i));

        s = System.getProperty("java.class.path").split(";");
        System.out.println("AppClassLoader 加载的类");
        System.out.println("  java.class.path:");
        Arrays.stream(s).forEach((i) -> System.out.println("    " + i));

        System.out.println("====================================================");

        ClassLoader classLoader = Test.class.getClassLoader();
        System.out.println("classLoader_name = " + classLoader.getClass().getName());
        System.out.println("classLoader_path = " + classLoader.getResource("").getPath());


    }
}

classLoader.getResource("")中的“”也是一种资源,和写文件名的没有什么区


猜你喜欢

转载自blog.csdn.net/hong10086/article/details/80285698
今日推荐