class加载过程
- Loading 加载:将class文件加载到内存
- Linking 链接
- Verification 验证:验证加载的class是否符合class文件标准(如:前4字节是否是cafebabe)
- Preparation 准备:给静态变量赋默认值等
- Resolution 解析:将类、方法、属性等符号引用解析为直接引用(常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用)
- Initializing 初始化:给静态变量赋初始值、执行静态代码块等(调用类初始化代码<clinit>)
类加载器(ClassLoader)
负责将class加载到内存
- 将class文件二进制数据加载到内存
- 解析二进制数据生成对应Class对象
- 默认采用懒加载方式,且只加载一次
- BootstrapClassLoader:负责加载lib/rt.jar charset.jar等核心类,C++实现
- 查看加载路径:System.getProperty(“sun.boot.class.path”)
- 这个类加载器加载的class,getClassLoader方法返回为null
- ExtClassLoader:负责加载扩展jar,jre/lib/ext/*.jar
- 查看加载路径:System.getProperty(“java.ext.dirs”)
- AppClassLoader:加载classpath中的class,通常指应用程序中定义的class
- 查看加载路径:System.getProperty(“java.class.path”)
- 自定义ClassLoader:应用程序自己定义的ClassLoader
双亲委派机制
- 父 -> 子:BootstrapClassLoader -> ExtClassLoader -> AppClassLoader
- 这里的父子不是继承关系,而是子加载器内聚父加载器(子加载器成员变量parent指向父加载器)
- 对双亲的理解:
- 既有子到父的过程也有父到子的过程?
- BootstrapClassLoader和ExtClassLoader是AppClassLoader的双亲?
- 当需要加载一个class时,由AppClassLoader进行加载(这里不考虑自定义类加载器)
- AppClassLoader不会直接加载,而是先检查自己是否已经加载过这个类,如果已加载则直接返回,否则交给ExtClassLoader进行处理
- 同样ExtClassLoader也不会直接加载,而是先检查自己是否已经加载过这个类,如果已加载则直接返回,否则交给BootstrapClassLoader进行处理
- BootstrapClassLoader先检查是否自己已经加载过这个类,如果已加载则直接返回,如果没有加载过,由于BootstrapClassLoader没有父加载器,所以会尝试在自己负责的类路径中查找对应的类,如果找到则进行加载并返回,如果没有找到则向下交给ExtClassLoader
- ExtClassLoader发现BootstrapClassLoader返回为null,则会尝试在自己负责的类路径中查找对应的类,如果找到则进行加载并返回,如果没有找到则向下交给AppClassLoader
- AppClassLoader同理,如果最终都没成功加载,则会抛出ClassNotFound异常
- 为什么使用双亲委派?
- 可以防止重复加载(优先从缓存中查找是否已加载过)
- 保证JDK核心类的优先加载
- 举例:假如应用程序也创建了java.lang.String这样的类,如果不用双亲委派,那么java核心库中的java.lang.String可能会被覆盖,从上面的加载流程可知,双亲委派机制中父加载器的加载逻辑会优先执行,所以java.lang.String这个类只会被BootstrapClassLoader加载,因为这个类存在于BootstrapClassLoader负责的类路径中
- 打破双亲委派机制?
- 自定义类加载器:继承ClassLoader,重写loadClass方法
- 典型场景1:Tomcat部署多个应用处于同一JVM虚拟机进程,假如不同应用间使用了同一个类的不同版本,就会发生冲突,所以需要自定义ClassLoader并打破双亲委派
- 典型场景2:热部署实现
自定义ClassLoader
自定义类加载路径,或直接加载指定的二进制数据等
比如:加载自己加密的class,可以防止class文件被篡改或反编译
- 继承ClassLoader
- 重写loadClass、findClass等方法