1.类在虚拟机的生命周期
--------------------------------------------------------------------------------------------------------
加载 ------------------> | 验证 --------------------> 准备 ------------------------>解析---------------------| |
| 连接阶段 | |
---------------------------------------------------------------------------------------------|----------
|
|
卸载<--------------------------- 使用 <------------------初始化
2.加载
步骤:(1)通过一个类的全限定名称来获取定义此类的二进制字节流(没有指明从哪里获取class文件,多形式加载
如:1.从Zip包从中读取,这很常见,最终成为日后的JAR,EAR,WAR格式的基础
2.从网络中获取,这种场景最典型的应用就是Applet
3.运行时计算生成,这种场景使用的最多的就是动态代理技术,如在java.lang.reflect.Proxy
4.由其他文件生成,典型场景是JSP应用,即由JSP文件生成对应的Class类
5.从数据库中读取等)
(2)将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
(3)在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
注:加载阶段与连接阶段的部分内容是交叉进行
3.验证:
(1) 文件格式验证
(2)元数据验证
(3)字节码验证
(4)符号引用验证
4.准备:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配.注意,这时候
进行内存分配仅包含类变量,而不包含实例变量,实例变量将会在对象实例化时随着对象一起分配到Java堆中.
一般情况下,类变量在这个阶段会被赋予初始值的数据类型的零值,因为这时候尚未开始执行任何Java方法,而是把类变量赋值
为零值后存放于类构造器<clinit>()方法之中,真正复制操作将在初始化阶段才会执行;但类变量被final修饰,即类字段的字段属性
表中存在ConstantValue,那在准备阶段类变量就会初始化为ConstantValue指定的值
5.解析
6.初始化:
初始化阶段是执行类构造器<clinit>()方法的过程
(1)<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{})中的语句合并产生的,编译器收集的顺
序是由语句在源文件中出现顺序决定
(2)<clinit>()方法与类的构造函数(实力构造器<init>())不同,他不需要显示地调用父类的构造器,虚拟机会保证在子类的<clinit>()方
法执行前,父类的<clinit>()方法已经执行完毕
(3)由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作
(4)<clinit>()方法对于类或接口来说不是必须的,如果一个类中没有静态语句块,也没有对变量进行复制操作,那么编译器可以不为
这个类生成<clinit>()方法
(5)接口中不能使用静态语句块,但仍有变量初始化赋值操作,因此接口与类一样都会生成<clinit>()方法,但接口与类不同的是,执行
接口<clinit>()方法不需要在父类的之前,只有父类接口中定义变量使用时,父类接口才会初始化,另外,接口的实现类在初始化时
也一样不会执行接口的<clinit>()方法
(6)虚拟机会保证一个类的<clinit>()方法在多线程环境中正确的加锁,同步,如果多线程同时去初始化一个类,那么只有一个线程会
去执行这个类的<clinit>()方法,而其他线程阻塞,直到活动线程执行<clinit>()完毕,如果一个类的<clinit>()方法中有耗时操作就
造成多个进程堵塞