加载的全过程,分为三步:加载
、链接
(验证、准备、解析)、初始化
、(使用、卸载)
加载
字节码的来源:
- 硬盘上的class文件
- 网络上的字节码(服务器、其他程序发来的)
- jar包、zip文件
- 数据库中的class文件
字节码被类加载器进行加载,将class文件字节码加载到内存中。加载完之后,会在方法区形成运行时数据结构。同时,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口
链接
将Java类的二进制代码合并到JVM的运行状态之中
- 验证:确保加载的类信息符合JVM状态,没有安全方面的问题
- 准备:正式为变量(static变量)分配内存,并设置类变量初始值的杰顿,这些内存走在方法区中进行分配
- 解析:虚拟机常量池内的
符号引用
替换为直接引用
的过程
类名称、变量名称、字符串,都是作为常量存在的。加载成字节码之后,每一个类都有一个常量池。常量池当中放置了符号引用。需要通过解析,将符号引用替换为直接引用。
初始化
-
初始化阶段是执行类构造器
<clinit>()
方法(不是普通的对象的构造器,平时看不到,也不能自己直接定义)的过程。<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。 -
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先发出其父类的初始化
-
虚拟机保证一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步(保证初始化时的线程安全)
我们来看一段代码:
package cn.hanquan.classloader;
public class Loader {
public static void main(String[] args) {
A a = new A();
System.out.println(A.width);
}
}
class A {
public static int width = 100;
static {
System.out.println("静态初始化块:A");
width = 300;
}
public A() {
System.out.println("A的构造器");
}
}
以上代码运行时,产生如下过程:
再加一个父类:
package cn.hanquan.classloader;
public class Loader {
static {
System.out.println("Loader的初始化块");
}
public static void main(String[] args) {
System.out.println("main中的第1条语句");
A a1 = new A();
System.out.println("A.width = " + A.width);
A a2 = new A();
a2.width = 666;//不建议这么做 应该使用A.width
System.out.println("A.width = " + A.width);
}
}
class A extends A_Father {
public static int width = 100;
static {
System.out.println("执行静态初始化块:A");
width = 300;
}
public A() {
System.out.println("执行A的构造器");
}
}
class A_Father {
static {
System.out.println("执行父类静态初始化块:A_Father");
}
}
Loader的初始化块
main中的第1条语句
执行父类静态初始化块:A_Father
执行静态初始化块:A
执行A的构造器
A.width = 300
执行A的构造器
A.width = 666
类加载全过程
类的主动引用,一定会发生初始化
- new一个类的对象
A a1 = new A();
- 调用类的静态成员(除了final常量)和静态方法
- 使用
java.lang.reflect
包的方法对类进行反射调用Class.forName("cn.hanquan.classloader.A);
- 当虚拟机启动,
java Hello
,则一定会初始化Hello类,说白了就是先启动main方法所在的类 - 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类
类的被动引用,不会发生类的初始化
- 调用
final
常量,不会发生初始化public static final int len=100;System.out.println(A.len);
因为常量是放在常量池中的,并不会初始化所在的类。 - 通过数组定义类引用,也不会初始化
A[] arr = new A[10];
- 当访问一个静态域(field)时,只有深圳声明这个域的类才会被初始化。
( 通过子类访问父类的静态域的时候,子类本身不会被初始化。)