深入理解Java ClassLoader原理
2017-03-26 15:16
357 查看
是什么ClassLoader
每一个类类型,对应有自己的Class类,通过someObject.getClass()或者
SomeClass.class的方式可以获取,而运行时这个Class类由JVM或者所处容器,甚至自定义的ClassLoader加载,这是一个必须的过程。由于所有类型的ClassLoader都是Java.lang.ClassLoader的实例,所以可以通过继承该类来实现自己的ClassLoader,改变类加载行为。再通俗点讲,ClassLoader就是加载.class文件的,.java文件通过编译器编译成字节码即.class文件,由ClassLoader加载得到可用于生成该类实例的Class类对象,最后,由该Class对象生成实例,如下图:
JVM类加载器
BootStrapClassLoader
启动类加载器用于加载java的核心类,例如:rt.jar中的类。它是其他类加载器的parent,也是唯一 一个没有parent的类加载器,处于ClassLoader的继承体系中的最高层级,由于它本身由C++编写,不是Java类,所以不需要其他类来加载它。
ExtClassLoader
扩展类加载器是BootStrapClassLoader的子加载器,用于加载环境变量
java.ext.dir路径下的jar包中的类。
AppClassLoader
应用类加载器是BootStrapClassLoader的子加载器,用于加载classpath下的类。
CustomClassLoader
当JVM类加载器不满足用户需求时,可以自定义类加载器,改写加载行为。
JVM类加载顺序:
用户自己的类加载器,把加载请求传给父加载器,父加载器再传给其父加载器,一直到加载器树的顶层。
最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转交给子类。
如果一直到底层的类加载都没有加载到,那么就会抛出异常ClassNotFoundException。
Tomcat类加载器
BootStrapClassLoader
在Tomcat中,BootstrapClassLoader同时兼顾加载Java核心jar包如rt.jar和扩展类路径java.lang.ext下的类。
SystemClassLoader
系统类加载器,加载catalina.bat(win)/catalina.sh(linux)下指定的类
CommonClassLoader
加载Tomcat及应用的通用类,位于CATALINA/lib下。
WebAppClassLoader
用于加载WEB-INF/classes和WEB-INF/lib路径下的类,加载顺序前者优先。
Tomcat类加载顺序
使用bootstrap引导类加载器加载
使用system系统类加载器加载
使用应用类加载器在WEB-INF/classes中加载
使用应用类加载器在WEB-INF/lib中加载
使用common类加载器在CATALINA_HOME/lib中加载
全盘负责 + 委托机制
ClassLoader之间,不是真正的继承关系,而是组合关系。classloader加载类时,使用全盘负责委托机制,可以分开两部分理解:全盘负责,委托。全盘负责机制:若类A调用了类B,则类B和类B所引入的所有jar包,都由类A的类加载器统一加载。
public class A { public void doSomething() { B b = new B(); b.doSomethingElse(); } }
B b = new B(); <===等同于===> B b = Class.forName(“B”, false,A.class.getClassLoader()).newInstance();
委托机制:类加载器在加载类A时,会优先让父加载器加载,如果找到,返回,如果父加载器加载不到,再找父加载器的父加载器(如果存在),一直找到bootstrap classloader都找不到,才自己去相关的路径去寻找加载。
机制的好处:这样做的好处有两点。
第一:重复加载问题。比如两个类A和类B都要加载某一个类C,如果不采用委派机制,当加载类A时,会加载一份C的字节码,加载类B时,C会被再加载一次,内存中将会出现两份一样的C字节码。然而在委派机制下,由于父类优先,C将会被父类(假如是Bootstrap)加载一次,当下次再加载时发现C的字节码是已有的,将会直接返回,不会重复加载。
第二:不会出现用户自定义类阻碍核心类加载的情况。比如用户自定义一个java.lang.String,结合前面的加载顺序图可以看到,由于父加载器优先加载,最先加载到内存的是java自身的String类,而非用户自定义的类。用户自定义的String类根本没有机会加载。
在特殊场景下,如果希望加载自定义类而非Java自身的类,可以自定义ClassLoader,改变加载行为。
这一点JVM和Tomcat有所区别,作为容器,tomcat根据自己的需求,重新实现了加载过程。JVM和Tomcat加载顺序的区别见上图。
源码分析
public abstract class ClassLoader { public Class loadClass(String name); protected Class defineClass(byte[] b); public URL getResource(String name); public Enumeration getResources(String name); public ClassLoader getParent(); }
loadClass方法,它接受一个全类名,然后返回一个Class类型的实例。
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 { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 抛出异常 } // 如果父加载器没有加载成功,才交由子加载器加载 if (c == null) { long t1 = System.nanoTime(); c = findClass(name); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
defineClass方法接受一组字节,然后将其具体化为一个Class类型实例,它一般从磁盘上加载一个文件,然后将文件的字节传递给JVM,通过JVM(native 方法)对于Class的定义,将其具体化,实例化为一个Class类型实例。
getParent方法返回其parent ClassLoader。
getResource和getResources方法,从给定的repository中查找URLs,同时它们也具备类似loadClass一样的代理机制,我们可以将loadClass视为:defineClass(getResource(name).getBytes())。
通常,我们可以通过继承java.lang.ClassLoader并实现findClass或loadClass逻辑来改变类加载行为。
如何唯一确定一个实例
在JVM中,仅靠全类名无法唯一确定一个实例,还需要类加载器。在JVM中,类型被定义在一个叫 SystemDictionary 的数据结构中,该数据结构接受类加载器和全类名作为参数,返回类型实例。如下图所示:其中,A同时被两个不同的类加载器L1和L2加载,虽然是相同的类,但不同类加载器加载得到的结果去完全不同。A和B是两个不用的类,由同一个类加载器加载,得到的自然也是不同的类类型。
一些关于ClassLoader的错误和排查方法
未完待续…举例
未完待续…相关文章推荐
- Java Thread&Concurrency(5): 深入理解Phaser实现原理
- Java Thread&Concurrency(11): 深入理解ThreadPoolExecutor及其实现原理
- 深入理解Java类加载器(1):Java类加载原理解析
- 【深入理解java集合系列】LinkedHashMap实现原理
- 深入理解Java类加载器(1):Java类加载原理解析
- Java Thread&Concurrency(9): 深入理解StampedLock及其实现原理
- 深入理解Java中的HashMap的实现原理
- 深入理解Java类加载器(1):Java类加载原理解析
- 深入理解Java中的HashMap的实现原理
- 【深入理解java集合系列】HashMap实现原理
- 深入理解Java类加载器(1):Java类加载原理解析
- 深入理解Java类加载器:Java类加载原理解析
- Java Thread&Concurrency(14): 深入理解条件队列(Condition)及其实现原理
- 深入理解Java事务的原理与应用
- Java Thread&Concurrency(2): 深入理解ConcurrentSkipListMap实现原理
- Java Thread&Concurrency(15): 深入理解ScheduledThreadPoolExecutor及其实现原理
- Java Thread&Concurrency(12): 深入理解AbstractExecutorService及其实现原理
- 深入理解Java类加载器(1):Java类加载原理解析
- 深入理解Java类加载器(1):Java类加载原理解析
- 深入理解JAVA多态原理