虚拟机类加载总结
时间:20180223
一、学习总结
知识储备
什么是静态方法?
什么是静态类?
什么是常量池?
什么是静态变量?
二、类加载机制流程
1.什么是类加载机制?
JVM把class文件加载到内存里面,并对数据进行校验、准备、解析和初始化,最总形成可以被JVM直接使用的java类型的过程。
2.类的加载流程图
3.类加载的过程--加载
”加载“是”类加载(Class Loading )“过程的一个阶段,切记不要混淆。
在加载阶段,虚拟机需要完成以下三个阶段。
1)将class文件记载到内存中。(通过类的全限定名来获取定义此类的二进制字节流)。
2)将上述字节流代表的静态数据结构(数据存在于class文件的结构)转化成方法区中运行时的数据结构(数据存在于JVM时的数据结构)。
3)在内存中(堆)生成一个代表这个类的class对象(java.lang.class),作为数据(方法区)访问入口。
4.链接
链接就是将java类的二进制代码合并到java的运行状态中的过程。
- 验证:确保加载的类符合JVM规范与安全(确保Class文件的字节流中包含的信息符合JVM规范与安全)
- 准备:为类变量(static变量)分配内存(在方法区中分配空间),并设置类变量的初始值。注意 -- 这里的类变量仅值被static修饰的变量,而不包括实例变量;实例变量将会在对象的实例化时随着对象一起分配在java堆中。例如public static int a = 123; 在此阶段a被初始化为0,其它数据类型参考成员变量声明。
//TO DO待加入Test代码
- 解析:虚拟机将常量池的符号引用转变成直接引用。例如"aaa"为常量池的一个值,直接把"aaa"替换成存在于内存中的地址。
符号引用:符号引用以一组符号(全限定名)来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标既可以。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
直接引用:直接引用可以使直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接应用是与虚拟机实现的内存布局相关。如果有了直接引用,那么引用的目标必定已经在内存中存在。
- 初始化:初始化是类加载过程的最后一步。初始化阶段是执行类构造器<clinit>()方法的过程。
在了加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中自定义的java程序代码(或者说是字节码)。
<clinit>()方法是由编译器自动搜集类中的所有类变量的赋值动作(准备阶段的 static a 正式被赋值为a)和静态变量与静态语句块static{}合并产生的。
注意 编译器手机的顺序是由语句在源文件中出现的顺序决定的,静态语句块只能访问到定义在静态语句块之前的变量;定义在之后的变量,在前面的静态语句块可以赋值,但是不能访问。
例如下列情况,会报错,但报错的地方不是赋值语句,而是访问语句。
三、类加载与初始化时机
1.类加载时机
当应用程序启动时候,所有的类会被一次性加载么?当然不能,因为一次性加载所有的类,内存资源有限,可能会影响应用程序的正常运行。那类什么时候才会被加载呢?例如在 A a = new A(),一个类真正被加载的时机是在创建对象的时候,才会去执行以上过程,加载类。当我们测试的时候,最新加载拥有main方法的主线程所在的类。
2.类初始化时机
主动引用(发生类初始化过程)
1.new 一个对象(使用new关键字实例化对象)
2.调用类的静态成员(除了final常量)和静态方法。
3.通过反射对类进行调用
4.虚拟机启动,main方法所在类被提前初始化。
5.初始化一个类,如果父类没有初始化,则先初始化父类。
被动引用(不会发生类的初始化)
1.当访问一个静态变量时,只有真正生命这个变量的类才会初始化。(例如:子类调用父类的静态变量,只有父类初始化,子类不初始化)。
2.通过数组定义类的引用。不会触发此类的初始化。
3.final变量不会触发此类的初始化,因为在编译阶段就存储在常量池中。
四、图解分析类加载
package com.amp;
public class ClassLoaderProduce {
static int d = 3;
static {
System.out.println("我是ClassLoaderProduce类");
}
public static void main(String [] args) {
int b = 0;
String c = "hello";
SimpleClass simpleClass = new SimpleClass();
simpleClass.run();
}
}
package com.amp;
public class SimpleClass {
static int a = 3;
static {
a = 100;
System.out.println(a);
}
public SimpleClass() {
System.out.println("对类进行加载!");
}
public void run() {
System.out.println("我要泡泡!");
}
}
步骤一:装载ClassLoaderProduce类,在方法区生成动态数据结构(静态变量、静态方法、常量池、类代码),并且在堆中生成java.lang.class对象;让后链接
步骤二:初始化--把static{}与静态变量合并放在类构造器<clinit>()当中,对静态变量赋值。1-5执行完毕。
步骤三:执行main方法,首先在栈里面生成一个main方法的栈帧,定义变量b、c,注意此处的变量b、c存储的常量池存储的变量的地址,如图所示。
步骤四:创建SimpleClass对象;与上面的步骤一类型:加载-链接-初始化。然后,调用run()方法的时候,它会通过classLoader局部变量的地址寻找到类的class对象并且调用run()方法。