现在的位置: 首页 > 综合 > 正文

类加载机制

2018年06月08日 ⁄ 综合 ⁄ 共 2073字 ⁄ 字号 评论关闭


转载请注明出处:http://blog.csdn.net/kai_wei_zhang/article/details/8234146

1、预先加载:JRE运行的开始会将Java运行所需要的基本类采用预先加载的方法全部加载到内存,如JRE的rt.jar里面所有的.class文件

2、类的生命周期:类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括七个阶段

加载——>验证——>准备——>解析——>初始化——>使用——>卸载

(其中类加载包括前面五个过程,即加载、验证、准备、解析、初始化。而验证、准备、解析属于连接阶段)

3、类加载的时机:虚拟机规范中没有强制规定什么时候加载,但加载、验证、准备(解析阶段是顺序是不确定的)必须在初始化之前完成,而虚拟机规范中严格规定了有且只有四种情况必须立即对类进行“初始化”

(1)遇到new、getstatic、putstatic 或 invokestatic这4条字节码指令时,如果类还没初始化,则触发初始化。对应场景是:new实例化对象时、读或设置一个静态字段时(被final修饰,已在编译器把结果放入常量池的静态字段除外),以及调用一个类的静态方法时

(2)对类进行反射调用时

(3)初始化子类。但父类还没有初始化时,先触发父类初始化

(4)当虚拟机启动时,需指定一个需要执行的主类(包含main方法),虚拟机会先初始化该类

4、类加载阶段的加载阶段。虚拟机需要完成的三件事

(1)通过类的全限定名来获得定义此类的二进制字节流

(2)将这个字节流所代表的静态存储结构按照虚拟机所需的格式存储在方法区

(3)在Java堆中生成一个代表这个类的java.lang.class对象,作为方法区这些数据的访问入口

5、验证阶段主要包括(因class的来源广泛,故验证是很有必要的)

文件格式验证——>元数据验证——>字节码验证——>符号引用验证

经过该阶段,字节流才进入内存的方法区进行存储,故后面三个阶段都是基于方法区的存储结构进行的

6、准备阶段:正式为类变量分配内存并设置类变量初始值的阶段,这些内存都在方法区中进行分配

区别:public static int value = 123;和public static final int value = 123;

只被static的在准备阶段过后初始化值为0,而不是123.因为此时还未开始执行任何方法,而把value赋值为123的putstatic指令是程序被编译后,存放在类构造器<clinit>()方法之中,所以把value赋值123是在初始化阶段才会被执行

而被static final 修饰的value,Javac编译时将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123(注意变量还应该是基本类型)

7、解析阶段:将常量池内的符号引用替换成直接引用的过程

符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中

直接引用与虚拟机实现的内存布局相关,引用的目标必定已经在内存中

8、初始化阶段:

(1)类加载到目前为止,用户应用程序可以参与的只有加载阶段,其他都是由虚拟机控制

(2)到了初始化阶段,才真正开始执行类中定义的java程序代码(或者说字节码)

(3)准备阶段——>变量已经赋过一次系统要求的初始值,而初始化阶段——>赋程序员给定的初始值(static final修饰的除外)——>初始化过程是执行类构造器<clinit>()方法的过程

(4)对<clinit>()方法的认识

<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的,编译器收集的顺序一定是先变量赋值,再静态语句(无论在源文件中出现的顺序如何)故在静态语句块中可以访问到类变量的初始化值了

<clinit>()方法和类构造函数(或实例构造器<init>() )不同,它不需要显示调用父类构造器,虚拟机会保证在子类构造器<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。故在虚拟机中第一个被执行的<clinit>()方法的类必定是java.lang.Object。由于父类的<clinit>()方法先执行,所以意味着父类中定义的静态语句块要优先于子类的变量赋值操作

有一道经典的笔试题(待补充)

<clinit>()方法对于类或接口并不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器不会为该类生产<clinit>()方法

接口中不能有静态语句块,但可以有静态变量,仍然可以有变量初始化的赋值操作,因此接口与类一样会生成<clinit>()方法

但是接口与类不同的是,执行接口的<clinit>()方法不会先执行父接口的<clinit>()方法,只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法

虚拟机保证了一个类的<clinit>()方法在多线程环境中被正确地加锁和同步

【上篇】
【下篇】

抱歉!评论已关闭.