ClassLoader学习


在这里插入图片描述

ClassLoader初始化

ClassLoader入口:sun.misc.Launcher

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 = ExtClassLoader.getExtClassLoader();
        
        // Create class loader used to launch the main application.
        loader = AppClassLoader.getAppClassLoader(extcl);

        //设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解
        Thread.currentThread().setContextClassLoader(loader);
        
  		...
    }

   

	/**
     * The class loader used for loading from java.class.path.
     * runs in a restricted security context.
     */
    static class AppClassLoader extends URLClassLoader {
    
    }

ExtClassLoader

 /*
     * The class loader used for loading installed extensions.
     */
    static class ExtClassLoader extends URLClassLoader {
    
    
        private static volatile Launcher.ExtClassLoader instance;

        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
    
    
            ...		
            instance = createExtClassLoader();	
            ...
            return instance;
        }

        private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
    
    
            ...
            // -Djava.ext.dirs=./plugin:$JAVA_HOME/jre/lib/ext
            // 获取java.ext.dirs对应的的所有目录,java.ext.dirs没配置则为空
         	// File[] var1 到 xxx.jar 这一层
			File[] var1 = Launcher.ExtClassLoader.getExtDirs();
			...
			// 注册、保存 File[] var1(每个File就是一个jar文件) 中的 每个File对应的 package路径信息
			// package路径信息具体就是: com/sun/image/ 这样
			// 建立  Map(new File(jarFile,jarName), new MetaIndex(List<jar中的package路径名称>, jar中是否只有class文件))
			MetaIndex.registerDirectory(var1[i]);
			...
			// 初始化、缓存 File[] var1 涉及到的url
			return new Launcher.ExtClassLoader(var1);
        }
    }
		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;
        }
	// 解析每个jar,并建立映射
     public static synchronized void registerDirectory(File jarFileDir) {
    
    
     	// 在 jarFileDir 目录下 拷贝生成 meta-index文件
     	// new File(file ,child ): 根据file 抽象路径名和 child 路径名字符串创建一个新 File 实例
        File jarFile= new File(jarFileDir, "meta-index");
        if (var1.exists()) {
    
    
            try {
    
    
                BufferedReader jarFileBufferedReader = new BufferedReader(new FileReader(jarFile));
                String lineStr = null;
                String jarName = null;
                boolean isOnlyHasClassName = false;
                ArrayList list = new ArrayList();
                Map jarMap = getJarMap();
                jarFile = jarFile.getCanonicalFile();
                lineStr = jarFileBufferedReader.readLine();
                if (lineStr == null || !lineStr.equals("% VERSION 2")) {
    
    
                    jarFileBufferedReader.close();
                    return;
                }

                while((lineStr = jarFileBufferedReader.readLine()) != null) {
    
    
                    switch(lineStr.charAt(0)) {
    
    
				    // 对于仅包含类文件的jar文件,我们输入’!'在jar文件名之前
                    case '!':
					// 对于包含资源和类文件的jar文件,我们在jar名称前加上’#’
                    case '#':
					// 对于只包含资源文件的jar文件,我们在jar文件名前加上’@’;
                    case '@':
                        if (jarName != null && list.size() > 0) {
    
    
                            jarMap.put(new File(jarFile, jarName), new MetaIndex(list, isOnlyHasClassName));
                            list.clear();
                        }

                        jarName = lineStr.substring(2);
                        if (lineStr.charAt(0) == '!') {
    
    
                            isOnlyHasClassName = true;
                        } else if (isOnlyHasClassName) {
    
    
                            isOnlyHasClassName = false;
                        }
                    case '%':
                        break;
                    default:
                        list.add(lineStr);
                    }
                }

                if (jarName != null && list.size() > 0) {
    
    
                    jarMap.put(new File(jarFile, jarName), new MetaIndex(list, isOnlyHasClassName));
                }

                jarFileBufferedReader.close();
            } catch (IOException var8) {
    
    
            }
        }

    }

每个jar文件至少包含清单文件,因此当我们说“只包含类文件的jar文件”时,我们不包含该文件。
meta-index文件如下,

% VERSION 2
% WARNING: this file is auto-generated; do not edit
% UNSUPPORTED: this file and its format may change and/or
%   may be removed in a future release
# charsets.jar
sun/nio
sun/awt
# jce.jar
javax/crypto
sun/security
META-INF/ORACLE_J.RSA
META-INF/ORACLE_J.SF
# jfr.jar
oracle/jrockit/
jdk/jfr
com/oracle/jrockit/
! jsse.jar
sun/security
com/sun/net/
! management-agent.jar
@ resources.jar
com/sun/java/util/jar/pack/
META-INF/services/sun.util.spi.XmlPropertiesProvider
META-INF/services/javax.print.PrintServiceLookup
com/sun/corba/
META-INF/services/javax.sound.midi.spi.SoundbankReader
sun/print
META-INF/services/javax.sound.midi.spi.MidiFileReader
META-INF/services/sun.java2d.cmm.CMMServiceProvider
javax/swing
META-INF/services/javax.sound.sampled.spi.AudioFileReader
META-INF/services/javax.sound.midi.spi.MidiDeviceProvider
sun/net
META-INF/services/javax.sound.sampled.spi.AudioFileWriter
com/sun/imageio/
META-INF/services/sun.java2d.pipe.RenderingEngine
META-INF/mimetypes.default
META-INF/services/javax.sound.midi.spi.MidiFileWriter
sun/rmi
javax/sql
META-INF/services/com.sun.tools.internal.ws.wscompile.Plugin
com/sun/rowset/
META-INF/services/javax.print.StreamPrintServiceFactory
META-INF/mailcap.default
java/lang
sun/text
javax/xml
META-INF/services/javax.sound.sampled.spi.MixerProvider
com/sun/xml/
META-INF/services/com.sun.tools.internal.xjc.Plugin
com/sun/java/swing/
com/sun/jndi/
com/sun/org/
META-INF/services/javax.sound.sampled.spi.FormatConversionProvider
! rt.jar
com/sun/java/util/jar/pack/
java/
org/ietf/
com/sun/beans/
com/sun/tracing/
com/sun/java/browser/
com/sun/corba/
com/sun/media/
com/sun/awt/
com/sun/management/
sun/
com/sun/jmx/
com/sun/demo/
com/sun/imageio/
com/sun/net/
com/sun/rmi/
org/w3c/
com/sun/swing/
com/sun/activation/
com/sun/nio/
com/sun/rowset/
org/jcp/
com/sun/istack/
jdk/
com/sun/naming/
org/xml/
org/omg/
com/sun/security/
com/sun/image/
com/sun/xml/
com/sun/java/swing/
com/oracle/
com/sun/java_cup/
com/sun/jndi/
com/sun/accessibility/
com/sun/org/
javax/

 	public ExtClassLoader(File[] var1) throws IOException {
    
    
		// 初始化URLClassPath(内部封装了var1对应的所有的url)
		// 设置parent classLoader、初始化 并行加载涉及到的parallelLockMap容器    
        super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
        
        // initLookupCache(this):初始化URl缓存
        SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }
	 // urls: the URLs from which to load classes and resources
	 // parent: the parent class loader for delegation
	 // factory: the URLStreamHandlerFactory to use when creating URLs
	 public URLClassLoader(URL[] urls, ClassLoader parent,
	                          URLStreamHandlerFactory factory) {
    
    
        // 设置父类加载器, 初始化 并行加载涉及到的parallelLockMap容器           
        super(parent);
        ...
        // 封装urls
        ucp = new URLClassPath(urls, factory, acc);
    }
// class ClassLoader
    private ClassLoader(Void unused, ClassLoader parent) {
    
    
    	// 设置父类加载器 
        this.parent = parent;
        // 如果开启了并行加载能力(本质上是成功loaderTypes.add(classLoader) ), 则 parallelLockMap = new ConcurrentHashMap<>();
        // ParallelLoaders.isRegistered(classLoader) == true
        // 实际上就是判断 loaderTypes.contains(classLoader)
        if (ParallelLoaders.isRegistered(this.getClass())) {
    
    
            parallelLockMap = new ConcurrentHashMap<>();
        	...
        } else {
    
    
            parallelLockMap = null;
            ...
        }
    }
// URLClassPath
	synchronized void initLookupCache(ClassLoader var1) {
    
    
		// getLookupCacheURLs(): 本地方法,初始化URL缓存
        if ((this.lookupCacheURLs = getLookupCacheURLs(var1)) != null) {
    
    
            this.lookupCacheLoader = var1;
        } else {
    
    
            disableAllLookupCaches();
        }

    }

以上是 ExtClassLoader的初始化

AppClassLoader

	static class AppClassLoader extends URLClassLoader {
    
    
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
    
    
        	// 获取设置的 java.class.path
            final String var1 = System.getProperty("java.class.path");
            // java.class.path的目录路径 ==> File[]
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            ···
            // File[] ==> URL[]
			URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
			// 初始化 URL[] var1x 下的url缓存,并设置 parentClassLoader
			return new Launcher.AppClassLoader(var1x, var0);
        }

        AppClassLoader(URL[] var1, ClassLoader var2) {
    
    
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }

        

        static {
    
    
            ClassLoader.registerAsParallelCapable();
        }
    }

并行化加载能力

public class URLClassLoader {
    
    
	 static {
    
    
        ... 
        // 开启 并行加载能力(本质上是loaderTypes.add(classLoader) )
        // 本质上,就是 loadClass时,将 synchronized 的范围 由 当前classLoader 改为 当前要加载的class
        ClassLoader.registerAsParallelCapable();
    }
}

关于ClassLoader.registerAsParallelCapable(); (并行加载):
JDK6Classloader.loadClass(String name)这个方法是synchonized的,如果应用里面有多个线程在同时调用loadClass方法进行类加载的话,那么锁的竞争将会非常激烈。

JDK7上,如果调用Classloader.registerAsParallelCapable方法,则会开启并行类加载功能,把锁的级别从ClassLoader对象本身,降低为要加载的类名这个级别。换句话说只要多线程加载的不是同一个类的话,loadClass方法都不会锁住。

// ClassLoader 
	protected static boolean registerAsParallelCapable() {
    
    
		// 为当前加载器注册并行能力
        return ParallelLoaders.register(callerClass);
    }

// ClassLoader
        static boolean register(Class<? extends ClassLoader> c) {
    
    
            synchronized (loaderTypes) {
    
    
            	// loaderTypes(是个set) 如果包含 当前classLoader的超类,就再次add当前classLoader,并返回true,代表注册成功
            	// 核心保证:如果一个classLoader想开启并行加载,必须保证 
            	//           这条加载器链路(当前classLoader 和 他的父亲、祖先 classLaoder 构成)
            	//           上的所有classLoader都开启了并行加载能力
            	// loaderTypes静态代码块初始化了: loaderTypes.add(ClassLoader.class); 
                if (loaderTypes.contains(c.getSuperclass())) {
    
    
                    loaderTypes.add(c);
                    return true;
                } else {
    
    
                    return false;
                }
            }
        }
// ClassLoader
 		// the set of parallel capable loader types
        private static final Set<Class<? extends ClassLoader>> loaderTypes =
            Collections.newSetFromMap(
                new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
        static {
    
    
            synchronized (loaderTypes) {
    
     loaderTypes.add(ClassLoader.class); }
        }

load class

// class ClassLoader
	protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
    
	    // getClassLoadingLock(name):就是 并行化加载的作用所在
	    // 如果没有开启并行化,锁的就是当前classLoader本身
	    // 否则锁的范围大大减小,只锁 当前加载的这个class对象
        synchronized (getClassLoadingLock(name)) {
    
    
            // 1、判断当前class是否被加载过,如果被加载国,从缓存中返回此class
            Class<?> c = findLoadedClass(name);
            if (c == null) {
    
    
                ...
                // 2、如果class没有被加载过,调用 父类加载器/虚拟机内置的加载器 加载class
                if (parent != null) {
    
    
                    c = parent.loadClass(name, false);
                } else {
    
    
                    c = findBootstrapClassOrNull(name);
                }
                ...
				// 3、如果 父类加载器/虚拟机内置的加载器 没有加载到class, 那么自己 再自己加载
                if (c == null) {
    
    
                    '''
                    c = findClass(name);
					...
                }
            }
            // 4、根据选项链接这个class
            //    真真正正的把这个普通的class对象 在jvm中真正“立起来”,
            //    就是把符号引用替换为直接引用,
            //    就是把这个静态的class文件转换为动态的运行时。
            //       a、检查:检查载入的class文件数据的正确性
            //       b、准备:给类的静态变量分配存储空间
            //       c、解析:将符号引用转化为直接使用
            if (resolve) {
    
    
                resolveClass(c);
            }
            return c;
        }
    }
// class ClassLoader
    protected Object getClassLoadingLock(String className) {
    
    
        Object lock = this;
        // 实际上,classloader构造函数中 会初始化 并行加载涉及到的parallelLockMap容器           
        // 如果开启了并行加载,parallelLockMap = new ConcurrentHashMap<>(); 
        // parallelLockMap 就不为null, 那么锁对象就变成了class了
        // 否则锁对象就是 classloader本身
        if (parallelLockMap != null) {
    
    
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
    
    
                lock = newLock;
            }
        }
        return lock;
    }

总结

classLaoder new阶段,只是缓存 此classLoader负责的那些class文件对象的URLs,只有 loadClass阶段才会各自加载到jvm中。

猜你喜欢

转载自blog.csdn.net/qq_43579103/article/details/118681546
今日推荐