new 一个对象的时候,会首先判断该类是否已经完成加载
把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型
类的整个生命周期:加载、验证、准备、解析、初始化、使用、卸载
验证、准备、解析又统称为连接
类的加载时机
初始化之前必须加载。初始化有明确的时机。
JVM对类的加载时机并没有强制规定,但是对类的初始化,有且只有下列五种情况必须立即对类进行初始化
类在初始化之前必须完成加载、验证、准备
(1)遇到new getstatic putstatic invokestatic 这四条字节码指令时,如果此时类没有初始化,就要先初始化
new
读取static字段
设置static字段的值
调用类的静态方法
(2)使用java.lang.reflect包中的方法对类进行反射调用时,如果此时类没有初始化,就要先初始化
(3)初始化一个类的时候,如果发现此时该类的父类没有初始化,就要先初始化其父类
(4)虚拟机启动的时候,初始化用户指定的主类(包含main方法的类)
(5)当使用动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为ref_getstatic ref_putstatic ref_invokestatic的方法句柄,并且,该方法所在的类没有初始化,先初始化
当且仅当上述五种情况,会触发类的初始化,主动引动
被动引用:(1)在子类中引用父类中定义的静态字段,只会初始化父类,不会初始化子类
(2)通过数组定义引用类,不会触发类的初始化
(3)常量在编译阶段会存入调用类的常量池中,本质上并未直接引用到定义常量的类,因此,不会触发定义常量的类的初始化
接口和类:
当一个类初始化的时候,其父类必须已经初始化完成,否则触发父类的初始化
在一个接口初始化的时候,并不要求其父接口全部初始化完成,只有在真正使用父接口时(例如,调用接口中的常量),才会触发父接口的初始化
类加载的过程
(1)加载
a.通过一个类的券限定名来获取定义此类的二进制流
b.将字节流锁代表的静态存储结构转化为方法区的运行时数据结构
c.在内存中生成一个代表该类的java.lang.Class类的对象,该Class对象作为方法区该类的各种数据的访问入口
加载完成之后,虚拟机外部的字节流按照Java虚拟机所要求的格式存储在方法区,然后在内存中实例化一个java.lang.Class对象
hotspot虚拟机中,Class对象虽然是对象,但它存放在方法区中,Class对象作为程序访问方法区中该类型数据的外部接口
(2)验证
验证,确保Class文件的字节流符合当前虚拟机的要求
文件格式(魔数、次版本号、主版本号、……)
元数据验证(继承的类是否正确,除了Object,所有的类都应该有父类)
字节码验证
符号引用验证:保证解析正常进行
(3)准备
为类变量分配内存并初始化为零值
(4)解析
将常量池中的符号引用解析为直接引用
(5)初始化
真正开始执行类中定义的Java代码
<cinit>()方法
该方法是编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生;静态语句块只能访问到定义在静态语句块之间的变量,定义在静态语句块之后的静态变量,在静态语句块中,只能赋值,不能访问
<cinit>()方法和构造器不同,构造器要显式调用才会执行,<cinit>()方法在子类<cinit>()执行之前,必须保证父类的<cinit>()执行完毕,父类的<cinit>()先执行,即 父类静态语句块先于子类的
<cinit>()并不是必需的,如果一个类没有静态语句块,就没有<cinit>()
接口中不能使用静态语句块,但是接口中有final static变量的赋值操作,所以,接口也会有<cinit>()方法,但是,接口的子接口或者实现类在执行<cinit>()时,不需要先执行父接口的<cinit>()
虚拟机保证在多线程环境中,只会有一个线程执行<cinit>()
类加载器
判断两个类是否一样:类本身和加载类的类加载器必须一样
(1)Bootstrap ClassLoader
加载%JAVA_HOME%lib目录中的类,启动类加载器无法被java程序直接引用
(2)Extension ClassLoader
加载%JAVA_HOME%libext目录中的类,扩展类加载器
(3)Application ClassLoader
应用程序类加载器,加载用户类路径下的类classPath
双亲委派模型
除了启动类加载器之外,任何类加载器都有父类加载器。类加载器之间的父子关系使用组合来实现
如果一个类加载器收到了加载类的请求,它首先判断该类是否已经被加载
如果已经加载完成,之间返回对应的Class对象
如果没有加载,该类加载器不会加载类,而是把加载类的请求交给父类加载器完成
如果当前类加载器没有父类加载器,证明是启动类加载器,直接使用启动类加载器加载类
如果当前类加载器有父类加载器,使用父类加载器加载类
如果父类加载器无法完成类的加载,子加载器才会尝试自己加载