类加载:虚拟机将描述类的数据从class 文件加载到内存中,对加载的数据进行验证,准备,解析,初始化;最后得到虚拟机认可后转化成直接可以使用的 java 类型的过程
1、类加载机制
-
加载
- 通过一个类的全限定类名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化成方法区的运行时数据结构
- 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口
-
验证(文件格式,元数据,字节码,符号引用等校验)
- 确保Class 文件的字节流中包含的信息符合当前JVM的要求,并且不会危害 JVM 自身的安全
- JVM 如果不检查输入的字节流,对其完全信任,很可能会因为载入了有害的字节流而导致系统崩溃
- 校验是 JVM 对自身保护的一种重要工作
-
准备(分配内存,变量初始值设置)
- 将为静态变量申请内存,并赋予初始值(基本类型为其默认值,引用类型为 null )
- 真正的赋值操作是在初始化阶段
-
解析
- JVM 将常量池内的符号引用替换成直接引用的过程
-
初始化
- 掉用类的初始化方法<clinit()> 为静态变量赋予实际的值,执行静态代码块
-
对于初始化,JVM 规范严格规定了有且只有 5 种情况必须立即对类进行初始化
- 遇到 new、getstatic、putstatic、invokestatic 这四个字节码指令时,如果类还没有进行过初始化,则要先触发其初始化,使用 new 关键字实例化对象时,读取或设置一个类的静态字段 ( static ) 时,(被static 修饰又被final 修饰的,已在编译期把结果放入常量池的静态字段除外),调用一个类的静态方法时
- 使用 java.lang.reflect 包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化
- 当初始化一个类的时候,如果发现其父类还未初始化,则需要先触发其父类的初始化
- 当 JVM 启动时,需要指定一个主类(即包含public static void main(String[] args) 方法的类),JVM 会对该主类触发初始化
- 当使用 JDK1.5 支持时,如果一个java.lang.invoke.MethodHandler实例最后的解析结果REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄时,并且这个方法句柄所对应的类没有进行过初始化,则先需要先触发其初始化
- 使用
- 卸载
2、双亲委派模型(避免重复加载 + 避免核心被篡改)
当一个类加载器收到一个类加载请求时,首先把加载任务委托给父类加载器,依次递归;
如果父类加载器可以完成类加载任务,就成功返回;
只有当父类加载器无法完成加载任务时,才自己去加载
好处:
- Java 类随着它的类加载器具备了一种带有优先级的层次关系
- 通过这种层次关系,可以避免类的重复加载,当父类已经加载了此类时,就没必要子 ClassLoader 再加载一次
- 防止重复加载同一个 .class 文件(自定义同名的 java 文件),通过委托机制,如果父类加载过了,就不再加载了
- 考虑到安全因素,Java 核心 API 中定义类型不会被随意替换
- 假设通过网络传递一个名为 java.lang.Integer 的类,通过双亲委派机制传递到最顶层加载器,而顶层加载器在核心 API 中发现这个名字的类,发现该类已被加载,并不会重新加载这个网络传递过来的同名的 java.lang.Integer ,而直接返回已加载过的类,这样可以防止核心 API 被篡改
核心加载类的方法:
1 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 2 synchronized (getClassLoadingLock(name)) { 3 // First, check if the class has already been loaded 4 Class<?> c = findLoadedClass(name); 5 if (c == null) { 6 long t0 = System.nanoTime(); 7 try { 8 if (parent != null) { 9 //调用父类的 ClassLoader 来加载 10 c = parent.loadClass(name, false); 11 } else { 12 // 没找到父类加载器,查找最顶层的 BootstrapClassLoader 来加载 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 if (c == null) { 20 // If still not found, then invoke findClass in order 21 // to find the class. 22 // 如果父类加载器都没找到,就直接调用查找类的方法去查找 23 long t1 = System.nanoTime(); 24 c = findClass(name); 25 // this is the defining class loader; record the stats 26 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 27 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 28 sun.misc.PerfCounter.getFindClasses().increment(); 29 } 30 } 31 if (resolve) { 32 resolveClass(c); 33 } 34 return c; 35 } 36 }
- 每一个 ClassLoader 都会持有一个父类的ClassLoader 对象
- 注意这里不是继承,而是组合关系
- 当调用当前的加载类的方法的时候,其实内部是调用父类的ClassLoader 来完成加载
- 如果最顶层的父类加载器抛出异常,说明父类无法完成加载请求,此时就由子类来完成查找类,加载类的过程了