类加载机制,将描述类的class文件加载到内存,并对数据进行校验、转换接卸、初始化,最终形成能被虚拟机直接使用的Java数据类型
Java中,类型的加载、连接、初始化都是在程序运行期间完成,动态加载和动态连接也是Java动态扩展的实现
类加载时机
一个类从加载进内存到从内存卸载,生命周期包括:
1、加载:加载时机虚拟机确定
2、验证
3、准备
4、解析
5、初始化,遇到如下5中情况时,必须立即初始化,即主动引用;其他情况不会触发初始化,即被动引用
1、遇到new(创建对象)、getstatic(获取静态字段)、putstatic(设置静态字段)、invokestatic(调用静态方法)时,这里静态字段不包括编译器放入常量池的被final修饰的静态字段
2、使用Java.lang.reflect包方法对类进行反射调用的时候
3、初始化一个类时,父类没有初始化的,先初始化父类
4、虚拟机启动时,用户需要指定个执行主类(包含main),虚拟机先初始化该主类
5、使用1.7的动态语言支持。如果个java.lang.invoke.MethodHandle解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且该类对应的类没有初始化,触发该类的初始化
初始化注意点:
1、对于静态字段,只有直接定义该字段的类才会被初始化,因此通过子类引用父类的静态字段,只会触发父类的初始化而不触发子类的,子类是否需要加载、验证取决于虚拟机实现
2、通过数组定义引用类,不会触发类的初始化
3、常量在编译期间会存入调用类的常量池,本质上没有直接引用到定义常量的类,因此不会触发定义类的初始化
4、对于接口的初始化,和类的初始化类似,但对于第三点,接口在初始化时,不要求父类全部完成初始化,只用在使用到父类接口时才会初始化
6、使用
7、卸载
其中2-4合称为连接;加载、验证、准备、初始化、卸载过程是确定的,解析过程为了支持运行时绑定(晚期绑定、动态绑定),也可以在初始化之后开始
类加载过程
1、加载阶段,对于非数组类,既可以使用系统的类加载器完成,也可以自定义类加载器,主要完成三件事
1、通过类的全限定名获取定义类的二进制字节流
2、将字节流中静态存储结构转换为方法区的运行时数据结构
3、在内存中(方法区)生成该类的Java.lang.class对象,作为方法区这个类的各种数据的访问入口
数组类的加载,本身不通过类加载器创建,由虚拟机直接创建,但是数组的元素类型由类加载器创建,数组类型的创建规则如下:
1、数组的组件类型(去掉一个维度的类型)是引用类型,递归采用上述类加载过程加载组件,数组C将在加载该组件类型的类加载器的类名称空间上被标识(类的唯一性由类和类加载器一起决定)
2、如果组件类型不是引用类型,则与引导类加载器关联
3、数组类的可见性与他的组件类型一致,如果组件类型不是引用类型,则可见性默认是public
2、验证阶段,确保class文件的字节流中包含的信息符合当前虚拟机的要求,且不会危害虚拟机的安全,主要完成四个阶段的验证
1、文件格式验证,检查字节流是否符合class文件规范,并能被当前虚拟机理解,保证输入的字节流能正确的解析并存储于方法区内,格式上符合class类型信息的要求,仅该阶段在二进制字节流上操作
2、元数据验证,对字节流的元数据进行语义分析,保证描述的信息符合Java语言规范
3、字节码验证,通过数据流和控制流分析,确定语义合法,对方法体进行验证,保证类的方法在运行时不会危害虚拟机
4、符号引用验证,发生在虚拟机将符号引用转换(解析阶段发生)成直接引用阶段,即常量池各种符号引用的匹配性校验
3、准备阶段,即为类分配内存并设置初始值的阶段,注意这里使用的内存空间为方法区空间,且仅包括static修饰的变量(实例变量在实例化的时候分配),初始值值零值,而如果是final static类型,即常量,则初始值为设置的值
4、解析,将常量池的符号引用转为直接引用
1、符号引用:一组符号描述引用的目标,被引用的对象不一定加载进内存,与内存布局无关,不同虚拟机可接收的符号引用是相同的
2、直接引用:直接指向目标的指针、相对偏移量或者能间接定位到目标的句柄,被引用对象必定加载进内存,与内存布局有关,不同虚拟机的直接引用一般不同
3、解析时间不确定
4、可以对同一个符号引用进行多次解析请求,除invokedynamic指令,但虚拟机可以对第一次解析结果进行缓存(运行时常量池记录直接引用并标记为已解析)从而避免多次解析,并且同一实体中之前成功过,后续也成功,否则都收到异常,invokedynamic用来支持动态语言调用的,对应的引用称为动态调用点限定符(dynamic call site specifier),即到程序实际运行该指令时才会解析
5、解析动作主要针对类或者接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符
5、初始化,即按照实际需要赋初值,即执行类构造器<cinit>()方法的过程
1、<cinit>()方法是虚拟机收集的所有类变量和静态语句块(static{ })中的语句合并而成的,顺序由源文件出现是顺序决定,注意静态语句块只能访问定义在静态语句块之前的变量,对于定义在后后面的变量,前面的静态语句块中可以赋值,但是不能访问
2、<cinit>()和类构造器(<init>())不同,不需要显式调用父类构造器,虚拟机保证父类<cinit>()先于子类的<cinit>()执行 -> 虚拟机中第一个被执行的<cinit>(),肯定是java.lang.Object
3、由于父类的<cinit>()方法先执行,那么父类定义的静态语句块优先于子类的变量的赋值操作
4、<cinit>()对于类和接口不是必须的,如果类中没有静态语句块,也没有赋值操作,则不生成<cinit>()
5、接口不能使用静态语句块,但可以赋值操作,即接口也可以有<cinit>(),但是执行接口的<cinit>()不一定要先执行父接口的<cinit>(),接口的实现类在初始化时也不会执行接口的<cinit>()
6、<cinit>()方法的执行时同步的,有可能因为某个<cinit>()耗时很长而造成进程阻塞,注意,其他线程虽然会被阻塞,但是当执行<cinit>()的线程退出<cinit>()后,其他线程被唤醒也不会再次进入<cinit>(),因为用一个类加载器下,同一个类只会被初始化一次
类加载器,实现类加载工作,类与加载该类的加载器一起,确定类在虚拟机中的唯一性,每个类加载器都有自己的类名称空间,一般有三种,从上到下依次为:
1、引导类加载器(BootStrap ClassLoader),负责<java_home>\lib下类库的加载,引导类加载器无法被程序直接引用
2、扩展类加载器(Extension ClassLoader),主要加载<java_home>\lib\ext目录下的类库,可以直接使用
3、应用程序类加载器(Application ClassLoader),即系统类加载器,负责classpath路径上指定的类库,如果程序没有自定义类加载器,就是程序中默认的类加载器
4、自定义类加载器
类加载器的双亲委派模型,除引导类加载器,其他加载器必须有父类加载器,这样的好处就是Java类随着它的类加载器具有了一种带优先级的层次关系,解决基础类的统一问题,其工作过程为
1、类加载器收到类加载请求,将请求委派给父类加载器,层层上传,最终达到引导类加载器
2、如果父类加载器无法加载,则子类加载器尝试加载
扩展,线程上下文类加载器(Thread Context ClassLoader),主要为了解决基础类调用用户代码的情形,如JNDI服务就需要使用上下文类加载器加载需要的SPI代码(父亲委托子类加载动作),该类加载器通过Thread类的setContextClassLoader()方法设置,未创建则从父类继承,全局范围内未设置则默认应用程序类加载器,Java中所有涉及SPI的加载动作基本采用线程上下文类加载器,如JDBC
相关文章
- 《深入理解Java虚拟机——JVM高级特性与最佳实践》学习笔记——虚拟机类加载机制
- 《深入理解Java虚拟机——JVM高级特性与最佳实践》学习笔记——虚拟机字节码执行引擎
- 【读书笔记】《深入理解java虚拟机·jvm高级特性与最佳实践》(一)-导图
- 《深入理解Java虚拟机——JVM高级特性与最佳实践》学习笔记——Java内存模型与线程
- 《深入理解Java虚拟机:JVM高级特性与最佳实践》笔记
- 《深入理解Java虚拟机——JVM高级特性与最佳实践》学习笔记——线程安全与锁优化
- 《深入理解Java虚拟机——JVM高级特性与最佳实践》学习笔记——自动内存管理机制
- 【阅读】深入理解Java虚拟机 ——JVM高级特性与最佳实践1
- 《深入理解Java虚拟机 JVM高级特性与最佳实践 》- 周志明 读书笔记
- 深入理解java虚拟机 -- jVM高级特性与最佳实践