# 虚拟机类加载机制

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制

# 类加载的时机

image-20210926222657606

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期会经历如下七个阶段:

  • 加载(Loading)
  • 验证(Verification)
  • 准备(Preparation)
  • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸载(Unloading)

其中,加载,验证,准备,初始化和卸载这五个阶段顺序是确定的,而解析阶段则不一定,在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性

《Java虚拟机规范》没有强制约束什么情况下要开始加载,但是对于初始化阶段,则严格规定了有且只有六种情况必须立即对类进行初始化:

  • 遇到new,getstatic,putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段,能够生成这四种指令的典型场景有:
    • 使用new关键字实例化对象
    • 读取或设置一个类型的static字段(被final修饰,已在编译器把结果放入常量池的static字段除外)
    • 调用一个类型的static方法的时候
  • 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化
  • 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
  • 当使用JDK7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandler实例最后的解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化
  • 当一个接口定义了JDK8新引加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化

# 类加载的过程

# 加载

在加载阶段,Java虚拟机需要完成以下三件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

《Java虚拟机规范》并没有指明从哪里获取一个类的二进制字节流,这就让各种Java技术层出不穷:

  • 从ZIP压缩包中读取,成为日后JAR,EAR,WAR格式的基础
  • 从网络中获取,最典型场景就是Web Applet
  • 运行时计算生成,使用最多的就是动态代理技术,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass()来为特定接口生成形式为“*$Proxy”的代理类的二进制字节流
  • 由其他文件生成,典型场景是JSP应用,由JSP文件生成对应的Class文件
  • 从数据库中读取,这种场景较少,比如有些中间件服务器(SAP Netweaver)可以把程序安装到数据库中来完成程序代码在集群间的分发
  • 可以从加密文件中获取,这是典型的防Class文件被反编译的保护措施,通过加载时解密Class文件来保障程序运行逻辑不被窥探

对于数组类而言,本身不通过类加载器创建,而是由Java虚拟机直接在内存中动态构造出来的,但数组类与类加载器仍然有密切关系,因为数组类的元素类型(Element Type,指的是数组去掉所有维度的类型)最终还是要靠类加载器来完成加载,

最近更新: 9/27/2021, 2:52:26 PM