类加载机制
1.类的生命周期
7个阶段:加载,验证,准备,解析,初始化,使用,卸载。其中验证,准备,解析3个部分统称为连接
2.类加载的过程
概述:
java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这个过程被称作虚拟机的类加载机制。
2.1加载
加载是将类的class文件读入到内存,并为之创建一个java.lang.Class对象.
在加载阶段 JVM具体做的3个事情:
-
通过一个类的全限定名来获取定义此类的二进制字节流
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
-
在内存生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
加载的时候从任何地方读取都可以,也可以在运行时动态的生成。
- 从本地系统中直接下载
- 通过网络下载 .class文件
- 从zip,jar等格式的文件中读取
- 从专有数据库中提取 .class文件
- 运行时计算生成。 动态代理技术(如反射)
2.2验证:
确保被加载的类的正确性。
目的是确保class文件中的字节流包含的信息符合《Java虚拟机规范》,保证这些信息被当作代码运行不会危害虚拟机自身安全。
包含文件格式验证,元数据验证,字节码验证,符号引用验证
2.3准备
正式为类中定义的静态变量(被static修饰的变量)分配内存(方法区)并设置类变量初始值的阶段。
注意: 对final修饰的静态变量直接赋初值(不是默认值)
2.4解析
虚拟机将常量池中的符号引用替换为直接引用。
注:class文件中除了有类的版本,字段,方法,接口等描述信息,还有一项信息是常量池,用于存放编译期生成的各种字面常量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池。
2.5初始化
为类的静态变量赋初值, 或者说初始化阶段是执行类构造器clinit()方法的过程。
clinit()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。
静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问
public class Test{
static{
i=0; //给变量赋值可以正常编译通过
System.out.print(i); //这句编译器会提示"非法向前引用"
}
static int i=1;
}
clinit()方法不需要显式地调用父类构造器,虚拟机会保证在子类的方法执行前,父类的已经执行完毕。
如果一个类没有静态语句块,也没有对变量的赋值操作,可以不生成clinit方法
接口中定义的变量使用时接口才会初始化。接口与类不同的是,执行接口的clinit()方法不需要先执行父接口的clinit()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的clinit()方法。
对象的构造方法编译为字节码是通过init方法,类似clinit收集过程,即收集成员变量,实例代码块,构造方法。
2.6类加载的时机
如果类没有进行过初始化,则需要先触发其初始化。虚拟机规定了5种情况必须立即对类进行初始化
- 创建类的实例(new 的方式)。
- 访问某个类或接口的静态变量,或者对该静态变量赋值,调用类的静态方法(类.xxx调用)
- 反射的方式
- 初始化某个类的子类,则其父类也会被初始化Java虚拟机启动时被标明为启动类的类,直接使用java.exe命令来运行某个主类(包含main方法的那个类)
- 当使用JDK1.7的动态语言支持时