类加载

类文件结构:

根据JVM规范,类文件结构如下:

类加载

  • 魔数:

       0-3字节,表示它是否是【class】类型的文件
       0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

  • 版本:

      4-7字节,表示类的版本00 34 (52)(16进制)表示是Java 8
      0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

常量池:

  • 8~9字节,表示常量池长度,00 23 (35)表示常量池有#l-#34项,注意#0项不计入,也没有值

      0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

  • 第#1项0a表示一个Method信息,00 06和00 15(21)表示它引用了常量池中#6和#21项来获得这个方法的【所属类】和【方法名】

      0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

  • 。。。

类加载阶段--加载:

将类的字节码载入方法区中,内部采用C++的instanceKlass描述java类,它的重要field有:

  • _java_mirror即java的类镜像,例如对string.来说,就是String.class,作用是把klass暴露给java使用
  • _super即父类
  • _fields即成员变量
  • _methods即方法
  • _constants即常量池
  • _class_loader即类加载器
  • _vtable虚方法表
  • _itable接口方法表

如果这个类还有父类没有加载,先加载父类
加载和链接可能是交替运行的

注意:

  • instanceKlass这样的【元数据】是存储在方法区(1.8后的元空间内),但_java_mirror是存储在堆中
  • 可以通过前面介绍的HSDB工具查看

类加载

类加载阶段--链接:

  • 验证:验证类是否符合JVM规范,安全性检查;用UE等支持二进制的编辑器修改HelloWorld.class的魔数,在控制台运行
  • 准备:为static变量分配空间,设置默认值
  1. static变量在JDK7之前存储于instanceKlass末尾,从JDK7开始,存储于_java_mirror末尾
  2. static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
  3. 如果static变量是final的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
  4. 如果static变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成
  • 将常量池中的符号引用解析为直接引用;经过解析之后,可以确切的知道类、方法等东西在内存中的位置;

类加载阶段--初始化:

发生的时机:

概括得说,类初始化是【懒惰的】

  •  main方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量或静态方法时
  • 子类初始化,如果父类还没初始化,会引发
  • 子类访问父类的静态变量,只会触发父类的初始化
  • Class.forName
  • new会导致初始化

不会导致类初始化的情况

  • 访问类的static final静态常量(基本类型和字符串)不会触发初始化
  • 类对象.class不会触发初始化
  • 创建该类的数组不会触发初始化
  • 类加载器的loadClass方法
  • Class.forName的参数2为false时