我们知道一个类创建的过程是: 首先是开辟空间,其次是初始化和指向空间地址,后面两个顺序可能不固定. 在此之前,JVM需要加载类文件到JVM,如果不加载那怎么知道该分配多大的存储空间呢,今天我们就来捋捋这个过程.
这次柯南要委托他爹妈了.
类加载的过程 JVM 加载类的机制有点类似懒加载 , 只有在类用到了的时候才会进行加载.
一个类加载进入虚拟机到能够给调用者使用的过程大概是这样的: 
1:加载, 将Class文件加载进JVM. 
2:验证, 验证Class文件的正确性. 
3:准备, 给类的静态变量分配内存,并赋默认值. 
4:解析, 将类的静态方法根据JVM指令替换成直接引用. 
5:初始化, 类的静态变量初始化 和 执行静态代码块. 
 
类经过上面5个过程,就加载进JVM了, 我们就已经可以使用其静态变量 以及 静态方法了. 如果需要使用其实例对象,那就在执行new 的过程,就是前面说的,开辟空间,初始化然后变量执行该地址.
加载 JVM 中有专门负责加载类的对象ClassLoader 类加载器.
类加载器可以分为4大类:
bootstrap class loader     引导类加载器 
extension class loader     扩展类加载器 
application class loader   应用类加载器 
自定义ClassLoader           自定义类加载器 
 
bootstrap class loader     引导类加载器: 负责加载jdk核心类库,比如:
 rt.jar , jce.jar , charsets.jar,jsse.jar 等
extension class loader     扩展类加载器: 负责加载jdk扩展类库(ext目录下的类库),比如:
cldrdata,dnsns,jaccess,jfxrt,localedata,nashorn,sunec,sunjce_provider,sunpkcs11,zipfs
application class loader   应用类加载器:负责加载classPath路径下的类文件.比如自己写的代码,第三方类库.
就像我们在记事本里面编程一样,我们通过 java命令将.java文件编译成.class 文件, 在通过javac 执行. java  javac 都是 c命令. 类的加载底层其实也是c去负责发起加载.  C++ 发起加载到 最终加载到class类对象.
过程大概如下:
4种类加载器之间的关系 顺着调用的顺序,我们来看下Launcher类;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public  class  Launcher    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;   private  static  URLStreamHandler fileHandler;   public  static  Launcher getLauncher ()       return  launcher;   }   public  Launcher ()       Launcher.ExtClassLoader var1;     try  {       var1 = Launcher.ExtClassLoader.getExtClassLoader();     } catch  (IOException var10) {       throw  new  InternalError("Could not create extension class loader" , var10);     }     try  {       this .loader = Launcher.AppClassLoader.getAppClassLoader(var1);     } catch  (IOException var9) {       throw  new  InternalError("Could not create application class loader" , var9);     }      ...   }   public  ClassLoader getClassLoader ()       return  this .loader;   } 
bootstrap class loader 引导类加载器 bootstrap class loader 负责加载jdk核心类库,如果我们通过下面代码来看看
1 2 ClassLoader classLoader = String.class.getClassLoader();         System.out.println(" classLoader = "  + classLoader); 
执行结果发现是
会不会感觉很意外? 因为bootstrap class loader 是C层面的,所以java中获取bootstrap class loader 时返回的是null.
extension class loader 扩展类加载器 我们先来看下ExtClassLoader的依赖关系.
ExtClassLoader 和 AppClassLoader继承了URLClassLoader  都是ClassLoader的子类.ClassLoader类中维护了一个private final ClassLoader parent;字段记录其层级关系.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static  class  ExtClassLoader  extends  URLClassLoader     public  static  Launcher.ExtClassLoader getExtClassLoader ()  throws  IOException  {           return  new  Launcher.ExtClassLoader(var0);         } }  public  ExtClassLoader (File[] var1)  throws  IOException       super (getExtURLs(var1), (ClassLoader)null , Launcher.factory);      SharedSecrets.getJavaNetAccess().getURLClassPath(this ).initLookupCache(this );    } 
从Launcher类的构造方法可以看到,最终调用 new Launcher.ExtClassLoader(var0) 其层级关系的parent传的是null,也就是类似我们获取不到的bootstrap class loader 引导类加载器.
extension class loader 扩展类加载器层级关系的上级是 bootstrap class loader 引导类加载器 
application class loader 应用类加载器 从Launcher类的构造方法中可以看到,AppClassLoader将ExtClassLoader作为上级传入其构造.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static  class  AppClassLoader  extends  URLClassLoader      final  URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this );     public  static  ClassLoader getAppClassLoader (final  ClassLoader var0)  throws  IOException          return  new  Launcher.AppClassLoader(var1x, var0);     }     AppClassLoader(URL[] var1, ClassLoader var2) {       super (var1, var2, Launcher.factory);       this .ucp.initLookupCache(this );     } } 
到这里,我们可以认定,引导类加载器,扩展类加载器,应用类加载器的关系如下:
自定义ClassLoader 自定义类加载器 我们自己也可以定义类加载器,实现URLClassLoader / ClassLoader自己定制加载类的逻辑.
如果是自定义的类加载器,其层级关系又该是怎样的呢?
首先我们来看下ClassLoader的构造,ClassLoader有一个无参构造方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 protected  ClassLoader ()          this (checkCreateClassLoader(), getSystemClassLoader());     }  public  static  ClassLoader getSystemClassLoader ()           initSystemClassLoader();         if  (scl == null ) {             return  null ;         }         SecurityManager sm = System.getSecurityManager();         if  (sm != null ) {             checkClassLoaderPermission(scl, Reflection.getCallerClass());         }         return  scl;     } 
其中scl = l.getClassLoader();  l 是Launcher.
getSystemClassLoader()最终获取的类加载器 是  AppClassLoader.
因此加入自定义加载器后,4 种加载器之间的关系应该是这样子的.
双亲委派机制 类加载的双亲委派机制 : 加载某个类,先从子层级委托父层级查找,如果父层级能找到就直接加载,所以父层级都找不到该类,再向下通知子层级,让子层级自己实现加载的机制.
打个比方来理解:
假如祖孙三代人,  孙子遇见一个问题, 就问父亲, 如果父亲知道问题就会直接告诉儿子,如果父亲也解决不了, 父亲在去问爷爷,爷爷如果知道就会告诉父亲,如果爷爷也不知道该问题, 爷爷就如实告诉父亲,”我解决不了这个问题, 你自己好好研究研究吧”,然后父亲也回来告诉儿子解决不了. 最终儿子自己想办法解决问题.
下面我们看看源码,看其是怎么实现的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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;      }  } 
这里我没有把自定义类加载器放进去,类加载的双亲委派机制大致流程如下:
jvm 类加载双亲委派机制好处 
1 沙箱安全机制. 
2 保证类加载的唯一性. 
3 加载效率更高. 
 
JVM 采用这种加载机制可以保证沙箱安全机制,以及加载类的唯一性.
沙箱安全机制: jdk提供的核心类库,保证核心类库API不会被篡改.
保证类加载的唯一性: 如果父层级类加载器以及加载该类,其子加载器就无需在加载了.
加载效率更高: 一般我们更多的是运行自己的代码而不是jdk的代码,通过这种自下而上的加载方式, 根据局部性原理,一个类用到了,说明后面很有可能还会被用到, 虽然加载自己的类每次都会绕一圈,但是如果加载过一次,后面就可以自己从 findLoadedClass方法中获取到. 如果不是使用这种方式,每次都得全局查找一次.
破坏双亲委派机制 破坏双亲委派机制  其实就是破坏这种自下而上的调用方式,中断调用parent.loadClass( ) 方法. 我们可以通过自定义加载器来达到这种效果.
自定义类加载器 项目中有两个一模一样的User 只是两个路径不一样, 根据前面所说的,双亲委派机制不会重复加载的特性, 下面打印出来的类加载器信息,应该都是AppClassLoader,通过实现自定义加载器,实现不同的类用不同的类加载器就可以轻松实现同时加载进两个不同版本的User 类进JVM中.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public  static  void  main (String[] args)  throws  Exception         System.out.println("========" );         User user = new  User();         user.print();         System.out.println("========"  + user.getClass().getClassLoader());         MyClassLoader loader = new  MyClassLoader("/Users/zhangwenhao/Desktop/test" );         Class<?> aClass = loader.loadClass("com.iz.study.pojo.User" );         Object o = aClass.newInstance();         Method print = aClass.getMethod("print" , null );         print.invoke(o, null );         System.out.println("========"  + aClass.getClassLoader());     } static  class  MyClassLoader  extends  ClassLoader          private  String classPath;         public  MyClassLoader (String classPath)               this .classPath = classPath;         }         @Override          protected  Class<?> loadClass(String name, boolean  resolve) throws  ClassNotFoundException {             synchronized  (getClassLoadingLock(name)) {                                  Class<?> c = findLoadedClass(name);                 if  (c == null ) {                     long  t0 = System.nanoTime();                     if  (!name.contains("com.iz.study" )) {                         c = this .getParent().loadClass(name);                     }                     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;             }         }         @Override          protected  Class<?> findClass(String name) throws  ClassNotFoundException {             try  {                 byte [] bytes = loadClassByte(name);                 return  defineClass(name, bytes, 0 , bytes.length);             } catch  (Exception e) {                 e.printStackTrace();             }             return  super .findClass(name);         }         private  byte [] loadClassByte(String name) throws  Exception {             name = name.replaceAll("\\." , "/" );             FileInputStream inputStream = new  FileInputStream(classPath + "/"  + name + ".class" );             int  available = inputStream.available();             byte [] bytes = new  byte [available];             inputStream.read(bytes);             inputStream.close();             return  bytes;         }     } 
Tomcat 底层也是用的类似这种逻辑实现,即使不同war用的相同类库不同版本也一样可以正常运行.