根据冯诺依曼定义的计算机模型,任何程序都需要加载到内存中才能与CPU进行交流
基础要点
- java中字节码.class文件包含了各种类信息,但是它是在本地硬盘中存储着的,因此只有把.class加载到内存中,通过JVM运行,它才可以与CPU进行交流,从而才可以实例化类。
- ClassLoader就是负责提前加载.class类文件到内存中
- 加载类时使用双亲委派模型(溯源委派加载模型),后文会提到
类加载器加载类的步骤
- Load(加载)
- 读取类文件,产生二进制流 -> 特定数据结构
- 校验魔法数、常量池、文件长度、是否有父类
- 创建对应类的java.lang.Class实例(表示这个类的类型信息)
- Link(链接)
- 验证:更详细的校验 比如:final是否合规,类型是否正确等
- 准备:为静态变量分配内存,并设定默认值
- 解析:确保类与类之间引用正确,完成内存布局
- Init(初始化)
- 执行构造器方法,如果赋值是通过其他类的静态方法完成的,那么会马上去解析另一个类
- 执行构造器方法,如果赋值是通过其他类的静态方法完成的,那么会马上去解析另一个类
Class 与 class
- 类加载是将.class字节码文件实例化成Class对象并进行相关的初始化,在这个过程中会初始化继承树上没有被初始化的父类,并且执行静态代码块,静态赋值语句等
- class是关键字,用来定义类,Class是所有class的类(这里不明白可以去看看反射)
- 反射中newInstance()是弱类型,只能调用无参构造方法,否则会抛出InstantiationException异常,而new是强类型校验,可以调用任何构造方法
类加载器如何定位到具体的类文件并读取?
- JVM在启动时会创建最根基的类加载器:Bootstrap,装载最核心的java类,如String、System等
- 第二层是在JDK9版本中,为Platform ClassLoader 即平台类加载器,用来加载扩展的系统类,如XML、加密压缩的功能类等,JDK9之前用的是Extension ClassLoader
- 第三层是Application ClassLoader的应用类加载器,主要加载用户定义的CLASSPATH路径下的类
- 区别:第二层和第三层类加载器是Java语言事先的,第一层使用C++实现的
双亲委派模型的类加载原则
- 低层次的类加载器,不能覆盖高层次类加载器已经加载的类
- 如果低层次的类加载器想加载一个未知类:
- 第一步:询问高层次类加载器是否已经加载?
- 第二步:高层次类加载器自己问自己是否已经加载了该类,如果没有就再问自己是否可以加载
- 第三步:如果高层次类加载器没有加载,也不能加载
- 第四步:则会通知发起请求加载的类加载器,准予加载
为什么要采用双亲委派模型
- 类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。
- 防止内存中出现多份同样的字节码
- 如果没有双亲委派模型而是由各个类加载器自行加载,当用户自己编写了一个System类,多个类加载器都去加载这个类到内存中,系统中将会出现多个不同的System类,那么类之间势必产生冲突,对象之间也无法进行比较。
自定义类加载器
场景
- 隔离加载类
- 修改类加载方式:类的加载类型并非强制,除了Bootstrap外,其它类加载器的时机可以自定义,进行动态加载
- 扩展加载源:比如从数据库网络等地方进行加载
- 防止源码泄露:加密字节码,需要解密