Java 类加载器(ClassLoader)
类加载器 ClassLoader
什么是类加载器?
- 通过一个类的全限定名来获取描述此类的二进制字节流这个动作放到Java虚拟机外部去实现, 以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为 "类加载器"。
 
类加载器的结构:

BootstrapClassLoader
- 启动类加载器, 用来加载<JAVA_HOME>/jre/lib 路径, -Xbootclasspath参数指定的路径以<JAVA_HOME>/jre/classes中的类。
 - 是由C++实现的
 
ExtClassLoader(Ext --> Extension)
- 拓展类类加载器, 它用来加载<JAVA_HOME>/jre/lib/ext路径以及java.ext.dirs系统变量指定的类路径下的类。
 
AppClassLoader
- 应用程序类类加载器, 主要加载应用程序ClassPath下的类 (包含jar包中的类)。
 - 是java应用默认的类加载器
 
用户自定义加载器
- 用户根据自定义需求, 自由的定制加载的逻辑, 继承AppClassLoader, 仅仅覆盖findClass(), 即将继续遵守双亲委派模型
 
ThreadContextClassLoader
- 线程上下文加载器, 它不是一个新的类型, 更像一个类加载器的角色, ThreadContextClassLoader可以是上述类加载器的任意一种, 但是往往是AppClassLoader
 
在虚拟机启动时会初始化BootstrapClassLoader, 然后在Launcher类中去加载ExtClassLoader, AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader, 并设置线程上下文类加载器。
Launcher: JRE中用于启动程序入口main()的类(sun.misc包下)
public class Launcher {
    // URL 流处理工厂
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    // 初始化发行器
    private static Launcher launcher = new Launcher();
    // 启动的类的路径
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    // 私有的类加载器
    private ClassLoader loader;
    // URL流处理器
    private static URLStreamHandler fileHandler;
    public static Launcher getLauncher() {
        return launcher;
    }
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            // 加载拓展类类加载器(先加载AppClassLoader的父类加载器)
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        try {
            // 加载应用程序类类加载器, 并将其父加载器var1(ExtClassLoader) 传入
            // 再加载AppClassLoader加载器
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        // 将当前线程的上下文加载器设置为当前加载器
        Thread.currentThread().setContextClassLoader(this.loader);
        // 获取java安全管理器信息
        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);
        }
    }
双亲委派模型:
- 当一个类加载器去加载类时先尝试让父类加载器去加载, 如果父类加载加载不了再尝试自身加载。
 - 双亲委派模型能保证基础类仅加载一次, 不会让jvm中存在重名的类。
 - java核心类都是BootstrapClassLoader加载的, 保证了java的安全与稳定性
 
ClassLoader的loadClass方法:
// 传入类名称 和 是否解析标签
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 {
                    // 如果父类不为空
                    if (parent != null) {
                        // 递归使用父类的loadClass方法
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果没有父类, 就通过Bootstrap类加载器加载该类
                        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.
                    // 如果没有找到, 就请求findClass来找到该类
                    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;
        }
    }
子类只需要实现findClass, 关心从哪里加载即可。parent需要自己设置, 可以放在构造函数设置。
注意点: AppClassLoader和ExtClassLoader都是Launcher的静态类, 都是包访问路径权限的。
如何自定义ClassLoader?
让我们来看一个apache-flink-core plugin包中的PluginLoader类的实现:
package org.apache.flink.core.plugin;
import org.apache.flink.annotation.VisibleForTesting;
import org.apache.flink.util.ArrayUtils;
import org.apache.flink.util.ChildFirstClassLoader;
import javax.annotation.concurrent.ThreadSafe;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
 * A {@link PluginLoader} is used by the {@link PluginManager} to load a single plugin. It is essentially a combination of a {@link ChildFirstClassLoader} and {@link ServiceLoader}.
 * 该类被用于加载一个单独的插件, 它本质上是一个由子类的第一个加载器和服务加载器组成的组合件
 * This class can locate and load service implementations from the plugin for a given SPI. The {@link PluginDescriptor}, which among other information contains the resource
 * URLs, is provided at construction
 * 该类可以从已得的SPI定位并且加载服务应用,插件描述器包含了在结构中提供的资源的URL
 */
@ThreadSafe
public class PluginLoader {
	/** Classloader which is used to load the plugin classes. We expect this classloader is thread-safe.*/
    // 被用于加载插件类的看类加载器, 我们期望该类加载器是线程安全的。
	private final ClassLoader pluginClassLoader;
	@VisibleForTesting
	public PluginLoader(ClassLoader pluginClassLoader) {
		this.pluginClassLoader = pluginClassLoader;
	}
    // 通过插件描述器 和 父类加载器 创建插件类加载器
	@VisibleForTesting
	public static ClassLoader createPluginClassLoader(PluginDescriptor pluginDescriptor, ClassLoader parentClassLoader, String[] alwaysParentFirstPatterns) {
		return new ChildFirstClassLoader(
			pluginDescriptor.getPluginResourceURLs(),
			parentClassLoader,
             // 合并总是父类优先模型的String数组 与 通过插件描述器 获取的 排除模型加载器 String数组
			ArrayUtils.concat(alwaysParentFirstPatterns, pluginDescriptor.getLoaderExcludePatterns()));
	}
	// 通过插件描述器 和 父类加载器 创建插件类加载器
	public static PluginLoader create(PluginDescriptor pluginDescriptor, ClassLoader parentClassLoader, String[] alwaysParentFirstPatterns) {
		return new PluginLoader(createPluginClassLoader(pluginDescriptor, parentClassLoader, alwaysParentFirstPatterns));
	}
	/**
	 * Returns in iterator over all available implementations of the given service interface (SPI) for the plugin.
	 * 在迭代器返回所有可获取的已得的实现了服务接口的插件
	 *
	 * @param service the service interface (SPI) for which implementations are requested.
	 * @param <P> Type of the requested plugin service.
	 * @return An iterator of all implementations of the given service interface that could be loaded from the plugin.
     * 返回一个所有已得的服务接口的可以从插件中被加载的实现类
	 */
	public <P extends Plugin> Iterator<P> load(Class<P> service) {
		try (TemporaryClassLoaderContext classLoaderContext = new TemporaryClassLoaderContext(pluginClassLoader)) {
			return new ContextClassLoaderSettingIterator<>(
				ServiceLoader.load(service, pluginClassLoader).iterator(),
				pluginClassLoader);
		}
	}
	/**
	 * Wrapper for the service iterator. The wrapper will set/unset the context classloader to the plugin classloader around the point where elements are returned.
	 * 服务迭代器的包装类, 在元素被返回时, 该包装器会设置或取消设置 上下文类加载器 到插件类加载器
	 * @param <P> type of the iterated plugin element.
	 * 迭代的插件元素类型
	 */
	static class ContextClassLoaderSettingIterator<P extends Plugin> implements Iterator<P> {
		private final Iterator<P> delegate;
		private final ClassLoader pluginClassLoader;
		ContextClassLoaderSettingIterator(Iterator<P> delegate, ClassLoader pluginClassLoader) {
			this.delegate = delegate;
			this.pluginClassLoader = pluginClassLoader;
		}
		@Override
		public boolean hasNext() {
			return delegate.hasNext();
		}
		@Override
		public P next() {
			try (TemporaryClassLoaderContext classLoaderContext = new TemporaryClassLoaderContext(pluginClassLoader)) {
				return delegate.next();
			}
		}
	}
}
												
											Java 类加载器(ClassLoader)的更多相关文章
- 深入理解Java类加载器(ClassLoader)
		
深入理解Java类加载器(ClassLoader) Java学习记录--委派模型与类加载器 关于Java类加载双亲委派机制的思考(附一道面试题) 真正理解线程上下文类加载器(多案例分析) [jvm解析 ...
 - 深入理解Java类加载器(ClassLoader) (转)
		
转自: http://blog.csdn.net/javazejian/article/details/73413292 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Ja ...
 - 浅析java类加载器ClassLoader
		
作为一枚java猿,了解类加载器是有必要的,无论是针对面试还是自我学习. 本文从JDK提供的ClassLoader.委托模型以及如何编写自定义的ClassLoader三方面对ClassLoader做一 ...
 - 潜水 java类加载器ClassLoader
		
类加载器(class loader)用于装载 Java 类到 Java 虚拟机中.一般来说.Java 虚拟机使用 Java 类的方式例如以下:Java 源程序(.java 文件)在经过 Java 编译 ...
 - java类加载器——ClassLoader
		
Java的设计初衷是主要面向嵌入式领域,对于自定义的一些类,考虑使用依需求加载原则,即在程序使用到时才加载类,节省内存消耗,这时即可通过类加载器来动态加载. 如果你平时只是做web开发,那应该很少会跟 ...
 - Java类加载器ClassLoader总结
		
JAVA类装载方式,有两种: 1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中. 2.显式装载, 通过class.forname()等方法,显 ...
 - Java类加载器(ClassLoader)
		
类加载的机制的层次结构 每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Jav ...
 - Java类加载器(死磕5)
		
Java类加载器( CLassLoader ) 死磕5: 自定义一个文件系统classLoader 本小节目录 5.1. 自定义类加载器的基本流程 5.2. 入门案例:自定义文件系统类加载器 5 ...
 - Java类加载器( 死磕9)
		
[正文]Java类加载器( CLassLoader ) 死磕9: 上下文加载器原理和案例 本小节目录 9.1. 父加载器不能访问子加载器的类 9.2. 一个宠物工厂接口 9.3. 一个宠物工厂管理 ...
 
随机推荐
- Unity初步 基本拼图实现
			
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; ...
 - Java源码-集合-ArrayList
			
基于JDK1.8.0_191 介绍 在Java中,对于数据的保存和使用有多种方式,主要的目的是以更少的资源消耗解决更多的问题,数组就是其中的一种,它的特点是所有的数据都保存在内存的一段连续空间中, ...
 - jdk动态代理和cglib动态代理底层实现原理超详细解析(jdk动态代理篇)
			
代理模式是一种很常见的模式,本文主要分析jdk动态代理的过程 1.举例 public class ProxyFactory implements InvocationHandler { private ...
 - Java - 使用hibernate配置文件 + JPA annotation注解操作数据库
			
本程序运行环境:IDEA. 实际上我对hiberbate与注解的关系还不是太清晰.据我所知注解都是Java JPA的,那么我的理解是:hibernate就应该只是通过这些JPA标识及hibernate ...
 - error C2664: “FILE *fopen(const char *,const char *)”: 无法将参数 1 从“LPCTSTR”转换为“const char *”
			
遇到这个问题,请打开本项目的Properties(属性)-------> Configuration Properties(配置属性)-------->General(常规)------- ...
 - 113、Java中String类之字符串文本分割IP地址
			
01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...
 - pycharm不能安装第三方库,错误代码Non-zero exit code (1) 的解决办法
			
pycharm版本 2019.3 大致意思是安装失败,建议的解决方案:尝试从系统终端运行此命令.确保使用正确的'pip'版本,该版本已为位于'C:\ Users \ G \ Desktoplgianf ...
 - 关于Simulink的sample time的问题
			
在对simulink建模的过程中,有时候会遇到sample time出现错误的问题,比如下图是我在使用simulink自带的Recursive least square Estimator最小二乘估计 ...
 - P1095 解码PAT准考证
			
1095 解码PAT准考证 (25分) PAT 准考证号由 4 部分组成: 第 1 位是级别,即 T 代表顶级:A 代表甲级:B 代表乙级: 第 2~4 位是考场编号,范围从 101 到 999: ...
 - 线程context
			
线程切换的时候,要保存当前运行状态,以便后续切换回来 CONTEXT结构体保存的是一堆寄存器 两个函数 //You cannot get a valid context for a running t ...