JVM底层之类加载(一)

Klass模型

Java的每个类,在JVM中都有一个对应的Klass与之对应 ,存储类的元信息如:常量池、属性信息、方法信息……

Klass的继承结构模型

Java中创建普通Java对象类对应在jvm存在形式为: InstanceKlass

Java创建array数组在对应在jvm存在的行为为 :ArrayKlass


InstanceKlass的实现类

InstanceMirrorKlass:是用来表示java.lang.Class,java代码中获取到的Class对象,实际上就是这个C++类的实例,存储在堆区,学名:镜像类。

InstanceRefKlass:用来表示java.lang.ref.Reference类的子类。

InstanceClassLoaderKlass:用于遍历某个加载器加载的类




ArrayKlass实现类

Java数组的元信息用ArrayKlass的子类来表示:

TypeArrayKlass是进行存储 基本类型数据

ObjArrayKlass 是进行存储对象数据(引用类型)

备注:java的数组不是静态数据类型,是动态数据类型,即在jvm运行时动态创建的


类的加载过程

类的生命周期是由7个阶段组成,但是类的加载说的是前5个阶段

Class 七个加载阶段

加载阶段:

   1.通过全限定名加载class文件
   2. 解析成运行时数据,即instanceklass数据

何时加载

主动使用时

1、new、getstatic、putstatic、invokestatic
2、反射
3、初始化一个类的子类会去加载其父类
4、启动类(main函数所在类)
5、当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化
预加载:包装类、String、Thread


校验阶段:

    1、文件格式验证
    2、元数据验证
    3、字节码验证
    4、符号引用验证


准备阶段:

   为静态变量分配内存、赋初值

  实例变量是在创建对象的时候完成赋值的,没有赋初值一说

  如果被final修饰,在编译的时候会给属性添加ConstantValue属性,准备阶段直接完成赋值,即没有赋初值这一步


解析阶段:

将常量池中的符号引用转为直接引用

解析后的信息存储在ConstantPoolCache类实例中

1、类或接口的解析
2、字段解析
3、方法解析
4、接口方法解析

 何时进行解析

    思路:

    1、加载阶段解析常量池时
    2、用的时候

    openjdk是第二种思路,在执行特定的字节码指令之前进行解析:

    anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、    invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield


初始化阶段:

执行静态代码块,完成静态变量的赋值
静态字段、静态代码段,字节码层面会生成clinit方法
方法中语句的先后顺序与代码的编写顺序相关


代码示例:

   public class Test_1 {   

public static void main(String[] args) {   

    System.out.printf(Test_1_B.str); 

  }}

class Test_1_A {   

public static String str = "A str";   

static { 

      System.out.println("A Static Block"); 

  }}

class Test_1_B extends Test_1_A { 

  static {   

    System.out.println("B Static Block"); 

  }}

1、输出是  A Static Block 、A str  将不输出B StaticBlocK ,因为使用对象的过程中并没有涉及到子类的变量
2、如果将str 静态变量定义在子类中则输出的信息则为A Static Block、B Static Block、A str   因为这是使用的变量为子类的信息,这是将会进行初始化
3、如果将子类的str类型加final变量修饰,则只输出 A str 
4、如果将子类的str 使用UUID.randomUUID方法赋值 ,则会输出A Static Block、B Static Block、A str  ,因为UUID所使用的发法需要进行实例化才可进行赋初始值。

则上述示例则可证明jvm使用的是懒加载

public class Test_1 { 

  public static void main(String[] args) { 

      System.out.println(Test_1_A.a); 

      System.out.println(Test_1_A.b); 

  }}

class Test_1_A {

public static int a;

public static int b = 1;

public static Test_1_A test1 = getInstance();

    public Test_1_A()  {   

    a++;        b++;    }

public static Test_1_A getInstance(){

return new Test_1_A();

}}

1】输出结果为 1,2 因为在执行实例化对象之前 a默认为0 b默认为 1 执行实例化后进行默认+1操作

2】如果将public static int b = 1; 放在实例化方法之后 则输出为1,1 因为在执行为初始化之后才执行的 调用b 则会将b值进行重新赋值这个结果就验证了 在类加载【方法中语句的先后顺序与代码的编写顺序相关】

你可能感兴趣的:(JVM底层之类加载(一))