类加载机制
- JVM把class(字节码)文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM可以直接使用的Java类型的过程。也就是最终加载形成class对象的过程。
具体过程分析
加载
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时二进制数据结构,并在堆中生成一个代表这个类的java.lang.Class对象作为方法区中类数据的访问入口。(编译时编译器会把程序中的一些常亮保存到class文件的常量池里,常量池是在编译时产生的。在把class文件加载到内存的这一过程中,会在对应内存区域里开辟一个常量池,把字节码文件中的常量加载进去。同时也会保存运行时产生的常量。)链接
将Java类的二进制码合并到JVM的运行状态之中的过程。
1.验证:确保加载的类的信息符合JVM规范且没有安全方面的问题。
2.准备:正式为类变量(static变量)分配内存空间并且设置类变量初始值(此时还是默认值,0)的阶段。这些内存都在方法区中分配。
3.解析:虚拟机常量池(一个类都有一个常量池)内的符号引用替换为直接引用的过程。(可以是直接地址,Java实现了动态绑定,这里链接的是程序中的计算方式的代码。并没有实际地址空间)初始化
初始化阶段是执行类的构造器
<clinit>()
方法的过程。类构造器<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。当初始化一个类的时候,如果发生该类的父类还没有进行初始化的话,会先触发其父类的初始化。
虚拟机会保证每一个类的
<clinit>()
方法在多线程的环境中被正确加锁和同步(线程安全)当访问一个Java类的静态域时,只有真正声明这个域的类才会被初始化(因为类的常量池是在编译时产生的,所以JVM在将这个类加载进虚拟机时就可以知道这个类是否拥有这个域,没有的话就不会将他初始化)
类的主动引用(一定会发生初始化)
- new一个类的对象(肯定会先初始化)
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当虚拟机启动,java Hello,则一定会初始化Hello类,即会先初始化main方法所在的类
- 如果父类还没有初始化则会先初始化父类,在初始化子类。
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明该域的类才会被初始化(通过子类引用父类静态变量,子类不会被初始化)
- 通过数组定义类引用,不会触发此类的初始化(例:
A[] as=new A[10];
此时A并不会初始化。) - 使用常量并不会触发此类的初始化(常量在编译时就存入调用类的常量池中了)