jvm 类的加载过程

Loading

1. 做了什么事?

将类的字节码文件加载到机器内存中,并在内存中构建出类的原型类模板对象

2. 步骤

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

3. Class实例的位置在哪

外部可以通过访问代表0rder类的Class对象来获取Order的类数据结构

java.lang.class实例是访问类型元数据的接口,也是实现反射的关键数据、入口。通过Class类提供的接口,可以获得目标类所关联的.class文件中具体的数据结构:方法、字段等信息

4. 什么是类模板对象

  1. 类在JVM内存中的一个快照,JVM将从字节码文件中解析出的常量池、类字段、类方法等信息存储到类中,这样JVM在运行期便能通过类模板而获取类中的任意信息,能够对类的成员变量进行遍历,也能进行方法的调用。
  2. 反射的机制即基于这一基础。如果JVM没有将类的声明信息存储起来,则JVM在运行期也无法反射
  3. 类模型的位置:存储在方法区(1.8之前:永久代:1.8及之后:元空间)

5. 二进制流有哪些获取方式

  •   虚拟机可能通过文件系统读入一个class后缀的文件(最常见)
  •   读入jar、zip等归档数据包,提取类文件
  •   事先存放在数据库中的类的二进制数据
  •   使用类似于HTTP之类的协议通过网络进行加载
  •   在运行时生成一段class的二进制信息等
  •   在获取到类的二进制信息后,Java虚拟机就会处理这些数据,并最终转为一个java.lang.class的实例
  •   如果输入数据不是ClassFilel的结构,则会抛出classFormatError

6. 数组类的加载有什么不同?

  • 创建数组类的情况稍微有些特殊,因为数组类本身并不是由类加载器负责创建,而是由JVM在运行时根据需要而直接创建的,但数组的元素类型仍然需要依靠类加载器去创建。创建数组类(下述简称A)的程:
  •  如果数组的元素类型是引用类型,那么就遵循定义的加载过程递归加载和创建数组A的元素类型
  •  JVM使用指定的元素类型和数组维度来创建新的数组类
  •  如果数组的元素类型是引用类型,数组类的可访问性就由元素类型的可访问性决定。否则数组类的可访问性将被缺省定义为public

 Linking

1. 验证

保证加载的字节码是合法、合理并符合规范的

2. 准备

  1. 为类变量(static变量)分配内存并设置默认初始值,即零值
  2. 不包含final修饰的static,其在编译时就会分配好默认值
  3. 如下,变量a在准备阶段值为0,到了初始化阶段才被赋值为1
private static int a=1;//prepare:a=0---> initial :a= 1  
public static final int value = 123; 此时value的值在准备阶段过后就是123。

3. 解析

将类、接口、字段和方法的符号引用转为直接引用

符号引用:就是一些字面量的引用,和虚拟机的内部数据结构和和内存布局无关。比较容易理解的就是在class类文件中,通过常量池进行了大量的符号引用。但是在程序实际运行时,只有符号引用是不够的,比如当如下println()方法被调用时,系统需要明确知道该方法的位置。

直接引用:得到类、字段、方法在内存中的指针或者偏移量。因此,可以说,如果直接引用存在,那么可以肯定系统中存在该类、方法或者字段。但只存在符号引用,不能确定系统中一定存在该结构

符号应用相当建房的图纸,直接引用建房子

图纸可以建筑多个房子

不过虚拟机规范并没有明确要求解析阶段一定要按照顺序执行。在HotSpot VM中,加载、验证、准备和初始化会按照顺序有条不紊地执行,但链接阶段中的解析操作往往会伴随着JVM在执行完初始化之后再执行

字符串:使用字符串常量时,就会在类中出现CONSTANT_String,它表示字符串常量,并且会引用一个CONSTANT_UTF8的常量项。虚拟机内部运行中的常量池中,会维护一张字符串拘留表(intern),它会保存所有出现过的字符串常量,并且没有重复项。只要以CONSTANT_String形式出现的字符串也都会在这张表中。使用String.intern()方法可以得到一个字符串在拘留表中的引用,因为该表中没有重复项,所以任何字面相同的字符串的String.intern()方法返回总是相等的。

Initialization

1. 概述

  1. 为类的静态变量赋予正确的初始值
  2. 类的初始化是类装载的最后一个阶段。如果前面的步骤都没有问题,那么表示类可以顺利装载到系统中。此时,类才会开始执行java字节码。(即:到了初始化阶段,才真正开始执行类中定义的java程序代码)、

2. 子类加载前先加载父类?

在加载一个类之前,虚拟机总是会试图加载该类的父类,因此父类的<clinit>总是在子类<clinit>之前被调用。也就是说,父类的static块优先级高于子类。

3. 哪些类不会生成<clinit>:方法?

  • 一个类中并没有声明任何的类变量,也没有静态代码块时
  • 一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时
  • 一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式
// 场景1:对于非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
public int num =1;
// 场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法
public static int num=1;
// 场景3:比加对于声明为static final的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
public static final int num=1;

4. 代码举例:static与finale的搭配问题

//    public static int a = 1;   //在初始化阶段赋值
//    public static final int INT_CONSTANT = 10;   //在链接阶段的准备环节赋值
//
//    public static Integer INTEGER_CONSTANT1 = Integer.valueOf(100); //在初始化阶段赋值
//    public static final Integer INTEGER_CONSTANT2 = Integer.valueOf(1000); //在初始化阶段赋值
//
    public static final String s0 = "helloworld0";   // 在链接阶段的准备环节赋值
    public static final String s1 = new String("helloworld1"); // 在初始化阶段赋值
//
    public static String s2 = "helloworld2";  // 在初始化阶段赋值
//
    public static final int NUM1 = new Random().nextInt(10);  // 在初始化阶段赋值

    static int a = 9;// 在初始化阶段赋值
    static final int b = a; //在 初始化阶段赋值

5. 类的初始化情况:主动使用VS被动使用

5.1. 主动使用的情况

  • 虚拟机规定,一个类或接口在初次使用前,必须要进行初始化。这里指的“使用”,是指主动使用
  • 主动使用只有下列几种情况:(即:如果出现如下的情况,则会对类进行初始化操作。而初始化操作之前的加载、验证、准备已经完成)
  • 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化
  • 当调用类的静态方法时,即当使用了字节码invokestatic指令
  • 当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic.或者putstatic指令
  • 当使用java.lang.reflect包中的方法反射类的方法时。比如:Class.forName("com.atguigu.java.Test")
  • 当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
  • 如果一个接口定义了defau1t方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类

5.2. 被动使用的情况

  • 被动使用不会引起类的初始化
  • 并不是在代码中出现的类,就一定会被加载或者初始化。如果不符合主动使用的条件,类就不会初始化
  • 当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。当通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类或接口的初始化。因为常量在链接阶段就己经被显式赋值了。
  • 调用classLoader类的1oadc1ass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化

6. -XX:+TraceClassLoading

Using

  1. 任何一个类型在使用之前都必须经历过完整的加载、链接和初始化3个类加载步骤。一旦一个类型成功经历过这3个步骤之后,便“万事俱备,只欠东风”,就等着开发者使用了。
  2. 开发人员可以在程序中访问和调用它的静态类成员信息(比如:静态字段、静态方法),或者使用new关键字为其创建对象实例。

Unloading(垃圾回收)

1. 类、类的加载器、类的实例之间的关系

在类加载器的内部实现中,用一个集合来存放加载类的引用。另一方面,一个class对象总是会引用它的类加载器,调用Class对象的getclassLoader()方法,就能获得它的类加载器,由此可见,代表某个类的class实例与其类的加载器之间为双向关联关系。

2. 何种情况类会被御载?

当Sample类被加载、链接和初始化后,它的生命周期就开始了。当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,Sample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值