类加载

类加载_第1张图片
类的生命周期

类加载时机

虚拟机规范中没有规定类加载的时机,但是规定了需要进行初始化的5种情况(而加载、验证、准备必须在此之前)。

  1. 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,也就是new一个对象或者读写一个类的字段或者调用一个类的方法。
  2. 通过反射调用一个类的时候。
  3. 初始化一个类是,如果父类还没有初始化,那么先初始化父类。
  4. 虚拟机启动时,指定的含main()的主类。
  5. JDK1.7的动态语言支持,MethodHandle是一个类字段或者方法的句柄,如果这个类没有初始化,那么初始化。

不会初始化的情况:

  1. 子类引用父类的静态字段和方法,子类不会初始化。
  2. 通过数组定义引用类,类不会初始化。
  3. 引用可以在编译期确定的常量类字段,不会引起类初始化,因为这个常量已经被使用的类放入自身的常量池。
  4. 一个接口初始化时不要求父接口完成初始化,只有在真正使用父接口时才需要初始化。

类加载的过程

指生命周期中的加载、验证、准备、解析和初始化

加载

  1. 虚拟机根据类的全限定名获取类的二进制字节流。
  2. 将字节流代表的静态存储结构转化为方法区的运行时数据结构。
  3. 内存中生成一个java.lang.Class对象,作为方法区这个类的数据的入口。
获取:
  1. ZIP(jar, war, ear...)
  2. 网络(applet)
  3. 运行时生成,Proxy动态代理
  4. 其他文件生成,JSP文件生成
  5. 数据库

加载类的阶段可以通过自定义类加载器来自定义,通过重写loadClass()方法来控制获取字节流。
但是数组类稍有特殊,例如数组类C:
(1)如果组件类型为引用类型,那么递归地加载这个组件类型,然后数组C将在加载了该组件类型的类加载器的类命名空间上被标识。
(2)如果组件类型不是引用类型,那么数组C会被标记为与引导类加载器关联
(3)数组类的可见性和组件类型一致

存储:

(1)二进制流以虚拟机所需格式存储在方法区,格式由虚拟机实现自定。
(2)实例化一个java.lang.Class类的对象,HotSpot将这个对象放在了方法区。

验证

验证字节流的信息符合虚拟机要求,并且不会危害虚拟机的安全。
字节码层面来说,字节码可以完成很多Java语言无法完成的操作,而字节码可以使用很多方式生成,不一定由Java源码编译而来,所以虚拟机为了保证自身安全,需要进行验证。

  1. 文件格式验证
  • 魔数
  • 主次版本号是否支持
  • 常量池中的常量类型
  • 指向常量的索引值指向是否有问题
  • 常量池UTF8类型常量的UTF8编码
  • Class文件是否有被删除或者附加的其他信息
    ......
    这一阶段的验证是基于字节流的,通过后,字节流才会进入方法区,后面的验证会基于方法区内的存储结构,而不再操作字节流。
  1. 元数据验证
    进行语义分析,以保证满足Java语言规范
  • 除了Object外应该有父类
  • 是否继承了final类
  • 如果不是抽象类,是否实现了所有需要实现的方法。
  • 字段与方法是否与父类矛盾(覆盖了父类final字段,不符合规则的重载)
    ......
  1. 字节码验证
    通过数据流与控制流分析,确定程序语义是合法符合逻辑的。
  • 保证操作数栈的数据类型与字节码指令能配合
  • 跳转指令不会跳转到方法体之外的字节码指令
  • 类型转换是有效的
    ......
    方法体的Code属性的属性表中在JDK1.6后添加了StackMapTable,用于描述方法的所有基本块开始时本地变量表和操作栈应有的状态,验证期间,不需要在根据程序推导这些状态的合法性,只需要检查StackMapTable属性中的记录是否合法即可,这样将类型推导转变为类型检查。
  1. 符号引用验证
    这个验证发生在解析阶段虚拟机将符号引用转化为直接引用的时候,对类自身以外的信息进行匹配性校验:
  • 字符串描述的全限定名能否找到对应的类
  • 指定类中是否存在符合方法描述符所描述的方法,是否存在简单名称所描述的字段
  • 符号引用中的类、字段、方法的访问性是否可被当前类访问

准备

为类变量在方法区中分配内存,并设零值(零值而非初始值),赋初始值的指令在()方法中,在初始化阶段执行。
但如果是final变量,字段属性表中包括ConstantValue的属性,那么会设为ConstantValue的值。

解析

将常量池中的符号引用替换为直接引用,包括常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等
详见《深入理解虚拟机》P221

初始化

执行()方法

  • ()方法内容来自于类变量的复制动作和static块,按声明顺序
  • ()方法与构造函数不同,不需要显示调用父类构造器,虚拟机保证父类的()方法已经执行
  • ()不是必须的
  • 接口没有初始化块,但是可以有赋值
  • 虚拟机保证一个类的()方法在多线程环境下能被正确地加锁同步,如果多线程同时去初始化一个类,那么只有一个线程会执行类的()方法,其他线程会阻塞。同一个类加载器下,一个类只会初始化一次。

类加载器

主要是加载阶段的“通过一个类的全限定名获取此类的二进制字节流”
用于类层次划分,OSGi,热部署,代码加密等领域

类与类加载器

任意一个类都要由加载它的类加载器和类本身一同确立它在Java虚拟机中的唯一性,也就是每个类加载器都有自己独立的类名称空间。

双亲委派模型
  • 启动类加载器(Bootstrap ClassLoader):
    \lib和-Xbootclasspath
  • 扩展类加载器(Extension ClassLoader):
    \lib\ext或者java.ext.dirs系统变量指定的类库
  • 应用程序加载器(Application ClassLoader):
    ClassPath
  • 自定义类加载器:
    父类加载器为应用程序加载器,通过组合实现

好处:
Java类随着类加载器有了层级关系

摘抄自《深入理解Java虚拟机》

你可能感兴趣的:(类加载)