根据《深刻理解Java虚拟机》提到“经过一个类的全限定名(packageName.ClassName)来获取描述此类的二进制字节(class文件字节)这个动做的代码模块就叫作类加载器(ClassLoader)”。java
一、一般类加载器的做用是加载资源(字节码文件)到java虚拟机中,想要在一个jvm 进程中惟一确认一个类,除了类的全限定名外,还须要指定它是由哪一个类加载器加载的。
二、好比咱们的类库须要经过远程网络获取,能够经过自定义类加载器从远程加载字节码文件。
三、java的字节码文件很容易反编译出来,一些核心的代码不想被反编译出来,能够对字节码进行加密,而后经过自定义的类加载器加载这些字节码,而后进行解码返回给虚拟机。
四、好比jvm的热加载和热部署等功能也须要自定义类加载器来完成。
.......git
一、Bootstrap ClassLoader : 该加载器是最顶层的类加载器,它是加载放在{Java_home}\lib目录 或者-Xbootclasspath指定路径下类库。github
二、Extension ClassLoader : 该类加载器负载加载{Java_home}/lib\ext目录 或者System.getenv("java.ext.dirs")系统变量路径下的类库。spring
三、Application ClassLoader : 该类加载器加载用户程序类路径下的类库,它是默认的程序的类加载器。api
一、双亲委派机制,双亲委派除了启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应该有本身父加载器。它们的实现不是经过继承来实现的,而是经过组合的方式。当加载某个Class时,当前类加载器会把这个加载请求委派给其父类加载器加载,同理父类加载器一样委派其它的父类加载器加载,直到无其父类类加载器加载为止,若是父类加载器加载失败,才会由其子类加载。springboot
二、使用双亲委派机制,有个明显的特征是:Java类随着它的类加载器一块儿具有了一种优先级的层次关系。好比rt.jar包中的java.lang.Object,因为它所在位置是由启动类加载器加载,因此Object类在程序的各类类加载器环境中都是同一个类。网络
三、双亲委派模型以下:
框架
能够看下ClassLoader 双亲委派模型的大体代码框架以下:jvm
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) { c = parent.loadClass(name, false); } else { //三、没有父类加载器,委托给BootstrapClassLoader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // 父类加载器没有加载到,则本身加载 long t1 = System.nanoTime(); c = findClass(name); // 记录该类加载的状态Stat. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } // resolve :true,须要对类进行连接(连接阶段包括:准备,解析,初始化类) if (resolve) { resolveClass(c); } return c; } }
一、经过以上能够知道,咱们能够继承ClassLoader 来实现本身的类加载器,而后重写findClass()方法,这些仍是保存了双亲委派机制。
二、当咱们重写findClass()方法时,获得该类的字节码后,须要调用defineClass()来放回Class<?>对象。maven
一、通常自定义的类加载器能够直接继承该类,该类加载器经过添加url路径来获取类。
二、能够在其构造函数上URLClassLoader(URL[] urls, ClassLoader parent)直接进行添加其URL。
三、也能够经过addURL(URL)添加其URL。
一、首先假设main-project为咱们本身编写的工程,其依赖某一api:service-spi。而service-spi的实现有不少,project-spi-impl是其中的一个。
二、当main-project仅依赖service-spi,而project-spi-impl不在工程的类加载路径下。因此须要自定义类加载器,从某个路径下的jar加载进来。CoreClassLoader就是自定义的类加载器。
三、CoreClassLoader继承自URLClassLoader,而后把相关搜索路径添加到该类加载器便可。
4.github:https://github.com/zhvqee/class-loader
一、SpringBoot 工程经过spring-boot-maven-plugin插件打包。把相关资源和依赖包都打到一个jar包中(all in one)。其包的结构以下:
BOOT-INF/classes:存放的是本工程的class文件
BOOT-INF/lib:存放的是本工程依赖的二方包和三方包。
META-INF/MANIFEST.MF:该文件记录了程序启动入口等。
o.s.b.loader包下就是springboot自定义的类加载器。
二、当咱们执行java -jar xxxx 时,会读取MANIFEST.MF下
Main-Class: org.springframework.boot.loader.JarLauncher,
该配置就是程序的路口。而咱们编写的Main方法不是真正的启动的入口。
三、当执行Launcher#launch()方法时,会把SpringBoot自定义的类加载器LaunchedURLClassLoader设置线程的上下文中,并经过该自定义类加载器加载咱们本身编写的main方法所在的类,而后利用反射调用main方法。代码片断以下:
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); mainMethod.invoke(null, new Object[] { this.args });
四、LaunchedURLClassLoader 类加载器继承URLClassLoader类加载器,它加载的路径就是可执行jar下BOOT-INF下的class文件和二方包、三方包。
一、JVM 加载的类主要通过如下几个步骤:加载,连接,初始化,试用,卸载。 二、Class.forName()默认是须要对加载的类进行初始化。 三、ClassLoader.load实际调用的是ClassLoader.load(className,false),false:表示不进行连接,不进行连接也就表明不会进行初始化的操做,类的静态块和静态对象都不会执行。 该文章