一、类加载的时机
- java 类名:java程序的入口类,需要先执行类加载,再执行main()
- 运行时,执行静态方法调用,静态变量操作等
- new对象的时候
- 通过反射创建一个类对象,然后就可以再通过反射,生成实例对象,或调用静态方法等
类加载只执行一次(已经执行类加载,方法区就已经有了类的信息,堆也有了类对象)
如果在多线程中,有需要执行类加载的代码(以上的几种时机): jvm执行类加载的时候,会进行synchronized加锁来保证线程安全(只执行一次)
二、类加载过程
- 加载: 加载class字节码(二进制数据)到方法区,在堆中,生成一个Class类对象
- 连接
1.验证: 验证class字节码数据,是否安全,及符合java虚拟机规范 2.准备: 静态变量设置为初始值(对象初始值就是null,基础数据类型,就是对应的初始值,如int就是0);常量(final修饰的)会设置为真实的值 3.解析: 将常量池内的符号引用替换为直接引用的过程,也就是初始化变量的过程
符号引用: 编译的class文件中,需要有变量/引用 到 值 对应的关系,此时还没有加载到内存中,就是用“符号引用”来表示这种关系 直接引用: 执行类加载,把class字节码加载到内存后,内存中体现的变量 到 值 的关系称为直接引用
- 初始化: 静态变量真正的初始化赋值;静态代码块初始化
三、类加载的机制:双亲委派模型
JDK默认的加载机制
类加载器:执行类加载,是Java虚拟机通过类加载器来加载类的 分为:
- BootStrap启动类加载器
- Ext扩展类加载器
- App应用/系统类加载器
- 自定义类加载器
类加载,是由层级关系的(一个类加载器,可能存在父类加载器)
1.双亲委派模型是什么
不直接执行当前类加载器的类加载代码,而是先要查找当前类加载器的父类加载器,父类加载器也是类似的,先找父类,找到最顶级的类加载器,开始执行类加载,找不到就交给下一级类加载器
简单说:(从下到上查找类加载器,从上到下执行类加载) 先不直接执行当前类加载器的类加载 而是先查找到最顶级的类加载器 从上往下执行类加载(上层找到class类就执行类加载,下层的类加载器就不会执行)
2.ClassNotFoundException 和 NoClassDefFoundError
ClassNotFoundException: NoClassDefFoundError: 类加载出错,会抛出这个异常。找到class文件了,但是类加载出错 方法区没有类信息,堆中也没有类对象
如果看到有NoClassDefFoundError或java.lang.ExceptionInInitializerError都是找到了class,但执行类加载出错
3.双亲委派模型的优缺点
优点: 确保安全,如Object、String类等,都是使用jdk提供的类;而不是使用到自己定义的java.lang.Object
特别的情况:黑客从网络上传过来一个java.lang.Object的二进制数据,如果没有双亲委派机制的保证,就会出现安全隐患
实际上,jdk中有提供类加载安全的验证操作 自定义的类,不能在java或javax开头的包下 (1)使用双亲委派机制,保证加载的先是jdk提供的类 (2)不使用双亲委派机制,加载的是自定义的类,但jdk也有安全的验证
缺点: 双亲委派机制进行类加载,保证了安全,但扩展性就没有那么好(灵活性降低) jdbc操作时,DriverManager.getConnection,就可以自动的执行数据库驱动包对应的初始化工作
数据库驱动包:不同数据库厂商提供的 初始化工作:存在不同的类,需要执行类加载 (1)静态变量初始化 (2)静态代码块初始化
只有双亲委派模型显然就不知道应该执行哪个数据库驱动类的类加载(jdk是不知道驱动类的类名的)
解决方法: 就是不采取双亲委派机制,而是采取一种SPI的机制 在驱动包METI-INF/services文件夹中,提供一个文件名是接口的全限定名(全包名.全类名) 内容是接口的实现类 内容里边使用ServiceLoader.load 这个api,就类似Class.forName(文件内容)
SPI机制,就没有遵循双亲委派模型来执行类加载: 而是采取,约定一个地方,我(jdk)能找到类的全限定名,然后就可以执行类加载
|