JVM类加载过程
JVM的类加载过程总体来说分为三个阶段:
1、类的加载
类的加载过程通过一个类的全限定名获取定义此类的二进制字节流,然后将这个字节流所代表的静态数据结构转化为方法区的运行时数据结构,最后在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口。
加载.class文件的方式:
- 从本地系统中直接加载
- 从zip压缩包中读取,成为日后jar、war格式的基础
- 运行时计算,比如动态代理
- 由其他文件生成,如jsp
- 从加密文件获取,比如使用自定义类加载器加载加密文件,来保证Class文件不会被反编译
2、链接阶段
a)验证
确保class文件的字节流中包含的信息符合虚拟机要求,保证被加载类的正确性,不会危害到虚拟机自身的安全,主要包括文件格式验证、元数据验证、字节码验证、符号引用验证。
b)准备
为类变量分配内存并且设置类变量的默认初始值,即零值。这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化,比如说在这里final static int a = 10;就不会把a设为0值了,而会直接分配内存,放入方法区中,并设初始值10。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象一起分配到堆中。
c)解析
通常解析操作往往伴随着JVM在初始化后再执行,将符号引用转换成直接引用,
3、初始化过程
初始化阶段就是执行类构造器方法<clint>()的过程。
此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
如果该类具有父类,必须保证先执行父类的<clint>()再执行子类的<clint>(),并且一个类的<clint>()在多线程下被枷锁。比如下方例子,a线程抢到执行权后,在初始化Test类的时候一直进入死循环,导致类始终无法初始化完毕,所以此时a线程无法初始完类,b线程也一直等待。
public class Main { public static void main(String[] args) { Runnable runnable = () -> { System.out.println(Thread.currentThread().getName() + "初始化开始"); Test t = new Test(); System.out.println(Thread.currentThread().getName() + "初始化结束"); }; Thread a = new Thread(runnable); Thread b = new Thread(runnable); a.start(); b.start(); } } class Test { static { if (true) { System.out.println("初始化当前类"); while (true) { } } } }
输出:
Thread-0初始化开始
初始化当前类
Thread-1初始化开始