类的加载过程

2018-12-26  本文已影响0人  程序设计法师

ClassLoader主要职责就是加载各种class文件到JVM中,他是一个抽象的类,给定一个class的二进制文件名,他会尝试加载并且在Jvm中生成构成这个类的各个数据结构,然后使其分布在Jvm对应的内存区域中。

类的加载过程分为三个阶段,加载阶段,连接阶段,和初始化阶段

加载阶段

主要负责查找并且加载类的二进制文件,其实就是class文件,将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态存储结构转换为方法区中运行时的数据结构,并且在堆中生成一个该类的class对象作为访问方法区数据结构的入口

栈内存:ClassLoader的引用,Aclass对象的引用,A对象的引用
堆内存:ClassLoader对象,A的class对象,A对象
方法区:A类的数据结构

不管某个类被加载了多少次,对应到堆内存中的class对象始终是同一个,他是类加载的最终产物。

连接阶段分为三个部分:验证、准备、解析

验证:

1.验证文件格式

  1. 元数据的验证(对class的字节流进行语义分析,确保符合Jvm规范)
  1. 字节码的验证
  1. 符号引用验证(主要是验证某个类的字段不存在,或者方法不存在抛出异常,反射的时候比较多见)
准备:

当一个class的字节流通过了所有的准备验证过程之后,就开始为该对象的类变量(静态变量)分配内存且设置初始值,类变量的内存会被分配到方法区中,不同于实例变量会被分配到堆内存中,所谓分配初始值,就是为相应的类变量给定一个相关类型在没有被设置值时的默认值
public static int a=10,在连接阶段初始值是0,
public final static int b=10,不会导致类的初始化,是一种被动引用,他不存在连接阶段,他在编译阶段就会被直接赋值为10

解析:

Simple simple=new Simple();
System.out.println(simple.name());
如上 我们可以通过simple访问Simple中的可见属性和方法,但是在字节码中,他会被编译成相应的助记符(符号引用),在类的解析过程中助记符还需要进一步解析才能正确的找到所对应的堆内存中的Simple数据结构。

初始化阶段主要为类的静态变量赋予正确的初始值(代码编写阶段给定的值)

每个类或者接口被java程序首次主动使用时,JVM才会对其进行初始化,所以程序包中那么多类并不是一次性全部被JVM初始化的,他很lazy.
静态语句块只能对后面的静态变量赋值,不能访问,否则无法通过编译
static{
System.out.println(x);
x=100;
}
private static int x=10;
虚拟机会保证父类的<clinit>方法最先执行,因此父类的静态变量总是能够得到优先赋值

主动使用的场景如下:

1.通过new关键字(会导致类的加载和最终初始化)TestApplication test=new TestApplication();
2.访问类的静态变量,读取和更新都会导致类的初始化 public static int x=10;但是,要记住,引用类的静态常量不会导致类被初始化,如 public final static int x=10;他是一个常量,不过需要计算的常量如:public final static int RANDOM=new Random().nextInt()例外,由于他在类的加载和和连接阶段无法对其进行计算,需要进行初始化后才能对其赋值。
3.访问类的静态方法也会导致类的初始化
4.对某个类进行反射操作会导致类的初始化 Class.forName("packageName+ClassName");
5.初始化子类会导致父类也被初始化,比如访问子类的静态变量或者静态方法,或者new一个子类,都会导致父类也被初始化,但是通过子类使用父类的静态变量或者静态方法只会导致父类被初始化,子类不会被初始化

上一篇 下一篇

猜你喜欢

热点阅读