URLClassLoader详解

URLClassLoader详解

ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是的,一次性加载那么多jar包那么多class,那内存不崩溃。本文的目的也是学习ClassLoader这种加载机制。。

  • java类加载机制原理
  • 双亲加载机制的优劣

java类加载机制

ClassLoader结构


ClassLoader结构

java中内置了很多类加载器,本文只讨论几个核心类加载器:

ClassLoader:所有类加载器的基类,它是抽象的,定义了类加载最核心的操作。所有继承与classloader的加载器,都会优先判断是否被父类加载器加载过,防止多次加载,防止加载冲突。。。

protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    //锁,防止多次加载,所以jvm启动巨慢
        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;
        }
    }

jdk 1.7为了提供并行加载class,提供ClassLoader.ParallelLoaders内部类,用来封装一组并行能力的加载器类型。这个一般是用不到的,有兴趣可以先看一下。但是需要知道ClassLoader是支持并行加载的。

    private static class ParallelLoaders

Bootstrap classLoader:位于java.lang.classload,所有的classload都要经过这个classload判断是否已经被加载过,采用native code实现,是JVM的一部分,主要加载JVM自身工作需要的类,如java.lang.、java.uti.等; 这些类位于$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。

  /**
     * Returns a class loaded by the bootstrap class loader;
     * or return null if not found.
     */
    private Class findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

    // return null if not found
    private native Class findBootstrapClass(String name);

SecureClassLoader:继承自ClassLoader,添加了关联类源码、关联系统policy权限等支持。

    public class SecureClassLoader extends ClassLoader

URLClassLoader:继承自SecureClassLoader,支持从jar文件和文件夹中获取class,继承于classload,加载时首先去classload里判断是否由bootstrap classload加载过,1.7 新增实现closeable接口,实现在try 中自动释放资源,但扑捉不了.close()异常

public class URLClassLoader extends SecureClassLoader implements Closeable

ExtClassLoader:扩展类加载器,继承自URLClassLoader继承于urlclassload,扩展的class loader,加载位于$JAVA_HOME/jre/lib/ext目录下的扩展jar。查看源码可知其查找范围为System.getProperty(“java.ext.dirs”)。

public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            //System.getProperty("java.ext.dirs");
            //在项目启动时就加载所有的ext.dirs目录下的文件,并将其初始化
            final File[] var0 = getExtDirs();

            try {
            //AccessController.doPrivileged特权,让程序突破当前域权限限制,临时扩大访问权限
                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();
            }
        }

ExtClassLoader 比较有意思的是,他使用的是顶级类(classloader)的loadclass方法,并没有重写,而且他的父亲加载器是null。。 。

public ExtClassLoader(File[] var1) throws IOException {
//parents is null
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }

AppClassLoader:应用类加载器,继承自URLClassLoader,也叫系统类加载器(ClassLoader.getSystemClassLoader()可得到它),它负载加载应用的classpath下的类,查找范围System.getProperty(“java.class.path”),通过-cp或-classpath指定的类都会被其加载,没有完全遵循双亲委派模型的,它重的是loadClass方法

        public Class loadClass(String var1, boolean var2) throws ClassNotFoundException {
            int var3 = var1.lastIndexOf(46);
            if (var3 != -1) {
                SecurityManager var4 = System.getSecurityManager();
                if (var4 != null) {
                    var4.checkPackageAccess(var1.substring(0, var3));
                }
            }

            //ucp是SharedSecrets获取的Java栈帧中存储的类信息
            if (this.ucp.knownToNotExist(var1)) {
            //顶级类classloader加载的信息
                Class var5 = this.findLoadedClass(var1);
                if (var5 != null) {
                    if (var2) {
                    //link过程,Class载入必须link,link指的是把单一的Class加入到有继承关系的类树中,不link一切都无从谈起了
                        this.resolveClass(var5);
                    }

                    return var5;
                } else {
                    throw new ClassNotFoundException(var1);
                }
            } else {
                return super.loadClass(var1, var2);
            }
        }

Launcher:java程序入口,负责实例化相关class,ExtClassLoader和AppClassLoader都是其内部实现类。。。

首先获取bootClassPath 的位置信息,交付于父类Classloader去加载位于$JAVA_HOME/jre/lib/rt.jar 的类

private static String bootClassPath = System.getProperty("sun.boot.class.path");
public static URLClassPath getBootstrapClassPath() {
        return Launcher.BootClassPathHolder.bcp;
    }

实例化相关ExtClassLoader和AppClassLoader,单线程运行,内部加载会对class文件加锁(所以启动慢的原因??)

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        //实例化ExtClassLoader,
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
        //实例化AppClassLoader,并把ExtClassLoader置为父类
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        //为线程设置上下文加载器,防止线程多次加载类
        Thread.currentThread().setContextClassLoader(this.loader);
        //加载安全管理
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

ClassLoader运行结构

主要基于 双亲加载机制

URLClassLoader详解_第1张图片

双亲加载机制:主要体现在ClassLoader的loadClass()方法中,思路很简单:先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载器加载失败,抛出ClassNotFoundException异常后,调用自己的findClass()方法进行加载。

双亲与判断等于:一般自定义的Class Loader可以从java.lang.ClassLoader继承,不同classloader加载相同的类,他们在内存也不是相等的,即它们不能互相转换,会直接抛异常。java.lang.ClassLoader的核心加载方法是loadClass方法

     Class  clazz = null; 
ClassLoader classLoader;
try {
classLoader =  new SpecialClassLoader ();
clazz = classLoader.loadClass("Hello");
System.out.println(clazz);
System.out.println(clazz.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class helloClazz;
helloClazz = Hello.class;
System.out.println("helloClazz:" + helloClazz);
System.out.println(helloClazz.getClassLoader());
System.out.println(helloClazz == clazz);
System.out.println(helloClazz.equals(clazz));

运行结果:

loadClass:Hello

specialLoadClass:test/Hello.class

loadClass:java.lang.Object

—resolveClass–

class com.test.javatechnology.classloader.test.Hello

com.test.javatechnology.classloader.SpecialClassLoader@106d69c

helloClazz:class com.test.javatechnology.classloader.test.Hello

sun.misc.Launcher$AppClassLoader@e2f2a

false

false

你可能感兴趣的:(架构)