一、Java虚拟机类加载机制

1、jvm将java源文件被编译成class文件,然后将class文件
加载到内存,并对数据进行校验、解析、初始化,最终形成
可以被虚拟机直接使用的java类型。这就是虚拟机的类加载机制。

2、类的生命周期如下:

<1>、加载:

根据全类名获取class文件的二进制流。
  将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  在内存中生成一个代表这个类的 java.lang.Class 对象(在堆内存),
  作为方法区这个类的各种数据的访问入口
  (通过堆内存里面的class对象来访问方法区的各种数据)。

<2>、验证

文件格式验证
  元数据验证(验证父子继承方面的是否合理)
  字节码验证(验证程序逻辑代码是否合理)
  符号引用验证(在解析阶段中发生,保证可以将符号引用转化为直接引用)

<3>、准备

为类变量(静态变量)分配内存并设置类变量初始值,这些静态变量在方法区中。

<4>、解析

虚拟机将常量池内的符号引用替换为直接引用的过程。

<5>、初始化

实际上就是执行汇编语言中的<cinit>方法,<cinit>方法中,
会从上到下执行带有static修饰符的语句(静态变量赋值和静态代码块),
如果当前类有父类,jvm可以保证先执行父类的<cinit>方法。

如果一个类中没有静态语句块,也没有对静态变量的赋值操作,
那么编译器可以不为这个类生成 <clinit>() 方法。

准备阶段就已经声明了静态变量了,所以静态变量赋值语句可以写在
静态变量声明语句前面

但是引用语句不能出现在声明语句前面。
这是因为编译器强制检查,静态变量只能先出现在左边。i=0,i出现在左边。
为什么要这样做?
是为了防止循环初始化和其他非正常的初始化行为

如果不强制检查,编译通过一下代码

 private int i = j;
 private int j = i;

i和j并没有被真正赋值,因为两个变量都是未初始化的
(Java规定所有变量在使用之前必须被初始化)

总结:

就是静态赋值语句可以在声明语句前面,但是使用语句不行。

二、类实例顺序

和类加载差不多,new 对象的时候,会先执行父类的构造方法,后执行子类的构造方法。

执行构造方法会执行汇编<init>指令


<init>指令:

会从上到下执行实例变量赋值和实例代码块。

所以类实例下载顺序是:

1、首先父类实例变量赋值和实例代码块从上往下依次执行,
   然后执行父类构造方法里面的语句

2、然后子类实例变量赋值和实例代码块从上往下依次执行,
   然后执行子类构造方法里面的语句

三、类加载的时机

1、虚拟机规范规定了有且只有 5 种情况必须立即对类进行“初始化”(主动引用)

<1>、执行4条字节码指令时:
	new(实例化对象,调用构造方法)
	getstatic(类名.静态变量名) 
	putstatic(类名.静态变量名=xxx) 
	invokestatic(调用类的静态方法,类名.静态方法名)

<2>、对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

<3>、当初始化类的父类还没有进行过初始化,则需要先触发其父类的初始化。
	(而一个接口在初始化时,并不要求其父接口全部都完成了初始化),
	也就是接口除外。

<4>、虚拟机启动时,用户需要指定一个要执行的主类
	(包含 main() 方法的那个类),虚拟机会先初始化这个主类。

<5>、动态代理时

2、(背动引用)不会触发初始化

通过子类引用父类的静态字段,不会导致子类初始化。
通过数组定义来引用类,不会触发此类的初始化。MyClass[] cs = new MyClass[10];
常量在编译阶段会存入调用类的常量池中,并没有直接引用到定义常量的类,
因此不会触发定义常量的类的初始化。
(一个类去调用另外一个类定义的常量,因为常量是在编译时已经存入常量池了,
 所以当前类是直接从常量池去取的值,和定义的这个类没有关系,因此不会触发
 定义常量的类的初始化。)