从外部文件载入类信息
类加载过程
大体上分为3个过程
装载Loading: 类文件从文件系统载入内存
链接Linking
校验Verifying: 检查读入数据是否符合Java规范
准备Preparing: 创建类结构储存信息,给静态变量分配存储空间填充默认值,如果final直接赋常量
解析Resolving: 常量池符号引用替换成直接引用,编译时,被引用目标尚未载入内存,用常量符号引用(Symbol Reference)代替
初始化Initializing: 执行类构造器<client>
过程,静态变量赋值,执行静态初始化块。不涉及实例因此只有静态行为
类加载器工作行为
动态加载:class第一次被引用的时候进行加载
不可卸载:class载入后不能再删除,可以删除整个加载器
层级代理:加载器具有层级结构,首先询问上级,实在没有再自己加载
类加载是延迟的,不必要就推迟
类只声明变量时不会加载,因此加载一个类并不会一并加载成员类
Sample 1 2 3 4 5 6 7 8 9 10 11 12 13 public class Test { public static void main (String[] args) { new A(); B b; } } class A { private B b; } class B {}
Sample 1 2 3 4 5 6 7 8 9 10 11 public class Test { public static void main (String[] args) throws InterruptedException, ExecutionException { Class c = A.class; } } class A { static { System.out.println("init" ); } }
类加载器类型
1 2 3 4 5 6 Bootstrap |---Extension |---System |---User-defined |---User-defined |---User-defined
Bootstrap Classloader: 启动类加载器,C实现,加载Java核心代码jre/lib
和-Xbootclasspath
指定的路径
Extension Classloader:扩展类加载器,加载扩展代码比如加密压缩等类库,jre/lib/ext
和java.ext.dirs
指定的路径
Application Classloader(System ClassLoader):应用加载器,可以进一步由开发者用代码自定义实现,加载-classpath
和-Djava.class.path
指定的路径
名义上启动类加载是扩展类加载的父级,但是代码中不能引用到
Sample 1 2 3 4 ClassLoader c = ClassLoader.getSystemClassLoader(); System.out.println(c); System.out.println(c.getParent()); System.out.println(c.getParent().getParent());
类加载机制
双亲委派(Parent Delegation)
一个加载器要加载一个类,先转给父级加载器,直到最顶层加载器,父级加载不了再由子加载器加载
好处是同一路径只能被加载一次,比如自定义了一个和库同名的java.lang.String,因为已经由启动类加载了库类,自定义类无法加载
负责制
一个类由一个类加载器加载,那么这个类所依赖和引用的类也由同一类加载器加载,除非显式指定其他加载器
也就是说Java类库中的类由启动加载器加载,那么类库中引用的其他类也是由启动加载器加载
类加载机制破坏
JDNI,JDBC接口都是位于核心库,由启动加载器加载,但是要调用具体的应用实现类,需要应用加载器加载
使用线程上下文的加载器Thread.currentThread().getContextClassLoader()
,如无特殊更改,默认是应用加载器
即效果是上级的启动加载器调用下级的应用加载器进行加载
类加载实现
ClassLoader是个抽象基础类
loadClass()是加载入口,默认不解析
loadClass()是个模板方法,实现委派逻辑
自定义加载器主要是实现findClass()方法, 逻辑主要解决两块内容
去哪里找类,即获取类的字节数组
找到后如何把字节数据转成Class对象,可以使用已提供的defineClass方法
ClassLoader.java 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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 public abstract class ClassLoader { protected ClassLoader (ClassLoader parent) { this (checkCreateClassLoader(), parent); } protected ClassLoader () { this (checkCreateClassLoader(), getSystemClassLoader()); } @CallerSensitive public final ClassLoader getParent () { if (parent == null ) return null ; SecurityManager sm = System.getSecurityManager(); if (sm != null ) { checkClassLoaderPermission(parent, Reflection.getCallerClass()); } return parent; } public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false ); } 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; } } protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } protected final Class<?> findSystemClass(String name) throws ClassNotFoundException { ClassLoader system = getSystemClassLoader(); if (system == null ) { if (!checkName(name)) throw new ClassNotFoundException(name); Class<?> cls = findBootstrapClass(name); if (cls == null ) { throw new ClassNotFoundException(name); } return cls; } return system.loadClass(name); } protected final Class<?> defineClass(String name, byte [] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null ); } }
显式加载
Class的forName方法可以传入加载器参数,指定由某一加载器加载
Class.java 1 2 3 4 5 @CallerSensitive public static Class<?> forName(String name, boolean initialize, ClassLoader loader) { return forName0(name, initialize, loader, caller); }
不指定加载器,则使用调用者的加载器,并且要执行初始化
Class.java 1 2 3 4 5 @CallerSensitive public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className, true , ClassLoader.getClassLoader(caller), caller); }
两个方法都标记成@CallerSensitive
表示只有通过启动加载器或者扩展加载器加载的类才能调用
动态加载forName默认就初始化
,而loadClass默认只加载没有链接初始化
,因此数据库加载驱动要使用forName才能执行静态初始化块
调试
通过JVM参数可以观察类加载细节
-XX:+TraceClassLoading
-XX:TraceClassUnloading
自定义加载器
自定义应用场景
卸载
条件
类所有实例已经回收
类对应的Class对象无引用
类加载器被回收
NoClassDefFoundError和ClassNotFoundException
两者都是找不到类,但有所区别
NoClassDefFoundError: 编译时存在,但在运行时找不到,是一种不该出现的错误
ClassNotFoundException:运行时动态加载找不到类,是一种可补救的异常
NoSuchMethodError调试
当有两个版本jar并存,一个方法只存在一个版本,加载错误就会找不到方法
可以通过Class类getProtectionDomain方法输出加载类是从哪个文件加载的
Sample 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 public class ClassLocationUtils { public static String where (final Class cls) { if (cls == null ) throw new IllegalArgumentException("null input: cls" ); URL result = null ; final String clsAsResource = cls.getName().replace('.' , '/' ).concat(".class" ); final ProtectionDomain pd = cls.getProtectionDomain(); if (pd != null ) { final CodeSource cs = pd.getCodeSource(); if (cs != null ) { result = cs.getLocation(); } if (result != null ) { if ("file" .equals(result.getProtocol())) { try { if (result.toExternalForm().endsWith(".jar" ) || result.toExternalForm().endsWith(".zip" )) result = new URL("jar:" .concat(result.toExternalForm()).concat("!/" ).concat(clsAsResource)); else if (new File(result.getFile()).isDirectory()) result = new URL(result, clsAsResource); } catch (MalformedURLException ignore) { } } } } if (result == null ) { final ClassLoader clsLoader = cls.getClassLoader(); result = clsLoader != null ? clsLoader.getResource(clsAsResource) : ClassLoader.getSystemResource(clsAsResource); } return result.toString(); } }