类加载的时机
一个类型从被加载到虚拟机内存中开始,到卸载为止,它的整个生命周期将会经历 加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段,其中其中验证、准备、解析这三个阶段统称为连接(Linking)。
有且只有6中情况必须对类立即进行“初始化”:
- 遇到new、getstatic、putstatic、或incokestatic这四条字节码指令时,如果类型没有经过初始化,则需要先触发其初始化阶段。
- 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
- 当初始化类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 当虚拟机启动的时候,用户需要指定一个需要执行的主类(main),虚拟机会先初始化这个主类。
- 当使用JDK7 新加入的动态语言支持时,如果一个java.lang.invok.MethodHandle实例最后的解析结果为REF_getStaic、REF_putStatic、REF_invokeStatic、REF_newInvokeSprcial四种类型的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。
- 当一个接种定义了JDK8新加入的默认方法(被default关键字修饰的接口方法),如果这个接口的实现类发生了初始化,那么该接口要在其之前被初始化。
类加载的过程
加载
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流多代表的静态存储结构转化为方法区运行时数据结构。
- 在内存中生成一个代表此类的java.lang.Class对象,作为方法区这个类的各种数据的访问接口。
验证
确保Class文件中的字节流中包含的信息符合《Java虚拟机规范》的要求,保证这些信息在运行后不会危害虚拟机自身的安全。
准备
为类的静态变量分配内存并设置类变量的初始值。这些变量所使用的内存都应在方法区中分配,单必须注意到方法区是一个逻辑上的概念,在JDK1.7 中HotSpot使用永久代来实现方法区是完全符合这一逻辑概念的;在JDK1.8 之后类变量则会随着Class对象一起妨碍Java堆中,这时“类变量在方法区”,就完全是一种对逻辑概念的表述了。
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化
进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序编码定制的主观计划去初始化类变量和其他资源(调用类的初始化方法)。
类加载器
双亲委派模型
- 启动类加载器(Bootstrap Class Loader)
- 扩展类加载器(Extension Class Loader)
- 应用程序类记载器(Application Class Loader)
如果一个类加载器收到了加载类的请求,他首先不会自己去尝试加载这个类,而是委派给父类的类加载器完成,每一个层次的类加载器都是如此,因此所有的类加载请求最终都应该传送到最顶层的类启动加载器中,只有当父加载器反馈自己无法完成这个加载请求,子加载器才会去尝试自己去完成加载。
破坏双亲委派模型
-
双亲委派模型出现之前,类加载器和抽象类java.lang.ClassLoader就已经存在,为了兼容这些代码,无法再以技术手段避免loadClass()被子类覆盖的可能性,只能在JDK1.2 之后 添加一个新的 protected 方法 findClass() 引导开发人员去尽量去重写这个方法二不是在loadClass中编写代码。
-
SPI
-
由于用户对程序动态性能的追求导致的,通常指的是代码热替换(Hot Swap)、模块热部署(Hot Deployment)等
Java模块化系统
模块下的类加载器(JDK 9)
- 扩展类加载器(Extension Class Loader)被平台类加载器(Platform Class Loader)替代。
- 平台类加载器和应用程序类加载器都不在派生自java.netURLClassLoader, 现在启动类加载器,平台类加载器、应用程序类加载器全部继承自 jdk.internal.loader.BuiltinClassLoader