在Java代码中,类型的加载,连接和初始化过程都是在程序运行期间完成的
- 加载:将已存在的.class文件文件从磁盘加载到内存中
- 连接:将类与类之间的关系确立好,并完成字节码相关的验证,校验,并将符号引用变为直接引用
- 初始化:对静态变量赋值
优点:
提供了更大的灵活性
增加了更多的可能性
类加载过程
调用一个类的main方法时,其执行步骤如下:
1.加载 —查找并加载类的二进制数据
将二进制形式的.class文件
读入JVM中
- 将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区中
- 在内存中创建一个
java.lang.class
对象(规范并未说Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区的数据结构 - 类的加载的最终产品是位于内存中的Class对象
- Class对象封装了类在方法区内的数据结构,并像Java程序提供了访问方法区内数据结构的接口
加载.class文件的方式
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip, jar等归档文件中加载.class文件
- 从专有数据库中提取.class文件
将Java源文件动态编译为.class文件
2.连接
类被加载后,就进入到连接阶段—就是将已经读入到内存中的类的二进制数据合并到虚拟机的运行时环境中去
连接包括三个步骤:
- 验证—确保被加载类的正确性(符合JVM规范)
- 准备—为类的
静态变量
分配内存,设置默认值,
在到达初始化之前,类变量都没有初始化为真正的初始值 - 解析—解析过程就是在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程
2.1 验证 – 确保被加载类的正确性(符合JVM规范)
- 验证内容包括
- 类文件的结构检测
- 语义检查
- 字节码检测
- 二进制兼容性检查
2.2 准备
为类变量分配内存,设置默认值,但是在到达初始化之前,类变量都没有初始化为真正的初始值
例如: 对与以下Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0
即:int
初始化为0,boolean
初始化为false
public class Sample{
private static int a = 1;
public static long b;
static{
b=2;
}
...
}
2.3 解析
把类中的符号引用转为直接引用,使用指针的方式指向目标对象内存地址
- 在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程
3.初始化—为类的静态变量赋予正确的初始值
初始化阶段.JVM执行类的初始化语句,为类的静态变量赋予初始值.
在程序中,静态变量的初始化有两种途径:
1). 在静态变量的声明处进行初始化
2). 在静态代码块中进行初始化
例如在以下代码中,静态变量a和b都被显示初始化,而静态变量从没有被显示初始化,它将保持默认值0
public class Sample{
private static int a = 1;
public static long b;
public static long c;
static{
b=2;
}
...
}
静态变量的声明语句,以及静态代码块都被看做类的初始化语句,JVM会按照初始化语句在类文件的先后顺序来依次执行他们.例如以下Sample类被初始化后,它的静态变量a的取值为4
public class Sample{
private static int a = 1;
public static long b;
static{
a = 2;
}
static{
a = 4;
}
public static void main(String args[]){
System.out.println("a="+a);
}
...
}
执行结果为
a=4
所有的JVM实现必须在每个类或接口被Java程序首次使用
是才初始化他们
- 类的初始化原则
- 假如这个类还没有被加载和连接,那么就先进行加载和连接
- 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类
- 假如类中存在初始化语句,那就依次执行这些初始化语句
-
类的初始化时机
只有当程序访问的静态变量或静态方法确实在类或当前接口中定义时,才可以认为是对类或接的主动使用- 主动使用(七种
重要
) - 除了上述七种情形,其他使用Java类的方式都被看作是
被动使用
,不会导致类的初始化 - JVM初始化一个类时,要求它的所有分类都已经被初始化,但是这条规则并不适用于接口
- 在初始化一个类时,并不会先初始化它所实现的接口
- 在初始化一个接口时,并不会先初始化它的父接口
- 主动使用(七种
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化,只有当程序首次使用特定接口的静态变量是,才会导致该接口的初始化
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
5.类的实例化
- 为新的对象分配内存
- 为实例变量赋默认值
- 为实例变量赋正确的初始值
java编译器为它编译的每一个类至少生成一个实例初始化方法,在java的class文件中,这个实例初始化方法被称为<init>
. 针对源代码中每一个类的构造方法,java编译器都产生一个<init>
方法
类的使用方式
- 创建对象的实例
- 调用对象的方法
类的使用类型
- 主动使用
- 被动使用