类加载
java的类加载过程分为加载、验证、准备、解析、初始化等阶段,加载、验证、准备、初始化的开始顺序是一定的,解析则可能会出现在初始化之后、使用过程中。
加载
类加载的第一个阶段就是加载类字节流,通过加载,jvm运行时数据区的方法区内就会产生该类相应的数据结构,并在heap中创建一个class对象来代表该类的数据结构,我们可以通过class对象访问方法区的类数据结构,通过反射对类进行操作。
验证
在加载的同时,就可以开始验证了,所谓验证,就是验证类的字节码是否符合java规范。编译为class文件时其实就包含了验证过程,但由于加载阶段加载的字节流未必是通过合法编译产生的class文件,所以jvm有责任对加载内容进行验证。当然,如果确认加载内容合法,可以选择关闭类加载的验证过程。只有通过了验证的字节序列,才会在方法区创建其对应的数据结构,之后的所有操作,都是针对该数据结构了。
准备
为加载、验证阶段在方法区生成的数据结构的类字段分配空间、赋零值。
例外,对于被赋值字面量static final字段来说,准备阶段就可以确定其真实值了,这也是在初始化之前唯一执行用户逻辑的地方。
解析
解析就是将常量池内的符号引用替换为直接引用,比如运行下面程序最终会打印加载Inner的信息,这是因为类字段inner在解析时会引发Inner的类加载,如果注释掉inner字段,则不会打印。注意,解析的目的只是将符号引用变为直接引用,即将符号引用变为内存中的真实地址(实际是逻辑地址),而一个类的字节序列变为方法区的数据结构只需要经过加载、验证即可,所以,此处的Inner类可能只经过了加载、验证。
1 public class Outer{ 2 private static Inner inner; 3 private static class Inner{ 4 } 5 //-XX:+TraceClassLoading 6 public static void main(String[] args){ 7 System.out.println("main"); 8 } 9 }
解析可能发生在初始化之后,比如将Inner inner变成Object obj = new Inner()。
解析阶段还可能发生在使用时,方法执行时都会将方法内未解析的符号引用转换为直接引用,比如执行下面程序,会打印Inner的加载信息。与前例不同的在于,new Inner()不但会引发加载、验证,还会引发解析、初始化、对象实例化等操作。如果此处只声明了Inner类型变量,而不使用Inner(所谓使用类,比如new、使用类字段、类方法等),则不会触发加载。
1 public class Outer{ 2 private static class Inner{ 3 } 4 //-XX:+TraceClassLoading 5 public static void main(String[] args){ 6 Inner outer = new Inner(); 7 } 8 }
初始化
注意,这里是类的初始化,要与实例的初始化区分,类的初始化就是执行用户对类字段的赋值操作。在类字段声明时赋值,以及在static块中对类字段赋值的逻辑,在java->class的编译后都会按顺序放入类初始化方法中。比如,如果实例化Test对象,输出1,但i最终会被赋值为2。
1 public class Test{ 2 static{ 3 i = 1; 4 System.out.println(i);//1,注意,实例块中禁止向前引用 5 } 6 private static int i = 2; 7 }
大致会生成如下的 类初始化方法:
1 //<clinit>方法 2 i = 1; 3 System.out.println(i); 4 i = 2;
类加载器
类加载器执行的类加载逻辑应当符合双亲委派模型,即优先父级类加载尝试加载,父级加载不成功时,子级才去尝试加载。我们自定义类加载器时,应当重写ClassLoader的findClass方法,而不是重写loadClass方法,因为loadClass方法遵守着双亲委派规则。
ClassLoader的loadClass方法:
1 protected Class<?> loadClass(String name, boolean resolve) 2 throws ClassNotFoundException 3 { 4 synchronized (getClassLoadingLock(name)) { 5 // First, check if the class has already been loaded 6 Class<?> c = findLoadedClass(name); 7 if (c == null) { 8 long t0 = System.nanoTime(); 9 try { 10 if (parent != null) { 11 c = parent.loadClass(name, false); 12 } else { 13 c = findBootstrapClassOrNull(name); 14 } 15 } catch (ClassNotFoundException e) { 16 // ClassNotFoundException thrown if class not found 17 // from the non-null parent class loader 18 } 19 20 if (c == null) { 21 // If still not found, then invoke findClass in order 22 // to find the class. 23 long t1 = System.nanoTime(); 24 c = findClass(name); 25 26 // this is the defining class loader; record the stats 27 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 28 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 29 sun.misc.PerfCounter.getFindClasses().increment(); 30 } 31 } 32 if (resolve) { 33 resolveClass(c); 34 } 35 return c; 36 } 37 }
当然也有破坏双亲委派模型的情况出现,比如tomcat7的类加载机制
这里的Bootstrap包括了Ext ClassLoader,common加载的class为webapps共享,webapp加载类时,首先使用SystemClassloader(遵循双亲委派),这是为了避免用户类覆盖java基础类,然后会使用webappN进行加载,如果加载失败再尝试双亲委派加载(即使用父级common加载器加载)。先webappN在common,而common是webappN的父级类加载器,这就破坏了双亲委派模型。
下面简单说一下tomcat加载的jar
对于System ClassLoader来说,tomcat会忽略系统变量CLASS_PATH,System ClassLoader会加载tomcat的bin目录下的相关jar:
common ClassLoader会加载tomcat的lib目录下的jar:
WebappClassLoader重写了loadClass方法:
1 //WebappClassLoader.loadClass (Tomcat 7.0) 2 3 4 @Override 5 public synchronized Class<?> loadClass(String name, boolean resolve) 6 throws ClassNotFoundException { 7 8 if (log.isDebugEnabled()) 9 log.debug("loadClass(" + name + ", " + resolve + ")"); 10 Class<?> clazz = null; 11 12 //检查当前ClassLoad是否已经停止了 13 // Log access to stopped classloader 14 if (!started) { 15 try { 16 throw new IllegalStateException(); 17 } catch (IllegalStateException e) { 18 log.info(sm.getString("webappClassLoader.stopped", name), e); 19 } 20 } 21 22 //检查缓存1,Class是否已经被当前Class"实例"装载过 23 // (0) Check our previously loaded local class cache 24 clazz = findLoadedClass0(name); 25 if (clazz != null) { 26 if (log.isDebugEnabled()) 27 log.debug(" Returning class from cache"); 28 if (resolve) 29 resolveClass(clazz); 30 return (clazz); 31 } 32 //ClassLoader检查缓存2,Class是否已经被父类"实例"装载过 33 // (0.1) Check our previously loaded class cache 34 clazz = findLoadedClass(name); 35 if (clazz != null) { 36 if (log.isDebugEnabled()) 37 log.debug(" Returning class from cache"); 38 if (resolve) 39 resolveClass(clazz); 40 return (clazz); 41 } 42 43 //ClassLoader装载系统classpath下面的类, 阻止webapp覆盖了J2SE的类 44 // (0.2) Try loading the class with the system class loader, to prevent 45 // the webapp from overriding J2SE classes 46 try { 47 clazz = system.loadClass(name); 48 if (clazz != null) { 49 if (resolve) 50 resolveClass(clazz); 51 return (clazz); 52 } 53 } catch (ClassNotFoundException e) { 54 // Ignore 55 } 56 57 //安全检查 58 // (0.5) Permission to access this class when using a SecurityManager 59 if (securityManager != null) { 60 int i = name.lastIndexOf('.'); 61 if (i >= 0) { 62 try { 63 securityManager.checkPackageAccess(name.substring(0,i)); 64 } catch (SecurityException se) { 65 String error = "Security Violation, attempt to use " + 66 "Restricted Class: " + name; 67 log.info(error, se); 68 throw new ClassNotFoundException(error, se); 69 } 70 } 71 } 72 73 //默认的返回值为false, 74 //delegate = false 75 //filter(name) 通过一个List指定哪些package里面的Class使用双亲委派模式,但是默认这个List是空 76 //比如包名以Java开头的,就需要使用双亲委派模式,不过这一点"部分"由 (0.2) 在上面已经实现了 77 boolean delegateLoad = delegate || filter(name); 78 79 //针对一些明确指定的package,使用双亲委派模式加载 80 // (1) Delegate to our parent if requested 81 if (delegateLoad) { 82 if (log.isDebugEnabled()) 83 log.debug(" Delegating to parent classloader1 " + parent); 84 ClassLoader loader = parent; 85 if (loader == null) 86 loader = system; 87 try { 88 clazz = Class.forName(name, false, loader); 89 if (clazz != null) { 90 if (log.isDebugEnabled()) 91 log.debug(" Loading class from parent"); 92 if (resolve) 93 resolveClass(clazz); 94 return (clazz); 95 } 96 } catch (ClassNotFoundException e) { 97 // Ignore 98 //如果(1)通过双亲委派加载发生异常, 忽略异常, 99 } 100 } 101 102 103 //尝试从本地加载Class( 如果(1)通过双亲委派加载发生异常, 忽略异常,也会走到这一步) 104 // (2) Search local repositories 105 if (log.isDebugEnabled()) 106 log.debug(" Searching local repositories"); 107 try { 108 clazz = findClass(name); 109 if (clazz != null) { 110 if (log.isDebugEnabled()) 111 log.debug(" Loading class from local repository"); 112 if (resolve) 113 resolveClass(clazz); 114 return (clazz); 115 } 116 } catch (ClassNotFoundException e) { 117 // Ignore 118 } 119 120 //最后依旧没有成功, 忽略所有配置,再次尝试一下双亲加载模式加载 121 // (3) Delegate to parent unconditionally 122 if (!delegateLoad) { 123 if (log.isDebugEnabled()) 124 log.debug(" Delegating to parent classloader at end: " + parent); 125 ClassLoader loader = parent; 126 if (loader == null) 127 loader = system; 128 try { 129 clazz = Class.forName(name, false, loader); 130 if (clazz != null) { 131 if (log.isDebugEnabled()) 132 log.debug(" Loading class from parent"); 133 if (resolve) 134 resolveClass(clazz); 135 return (clazz); 136 } 137 } catch (ClassNotFoundException e) { 138 // Ignore 139 } 140 } 141 142 //最后如果依旧没有成功,则抛出异常 143 throw new ClassNotFoundException(name); 144 145 }
参考:http://flyfoxs.iteye.com/blog/2114149
https://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html