jvm中的变量分类-类加载
变量的分类:
- 按照数据类型分:
- 基本数据类型
- 引用数据类型
- 按照在类中声明的位置分:
- 成员变量:在使用前,都经历过默认初始化赋值
- 类变量:linking的prepare阶段:给类变量默认赋值 —》initial阶段:给类变量显式赋值即静态代码块赋值
- 实例变量:随着对象的创建,会在堆空间中分配实例变量空间,并进行默认赋值
- 局部变量:在使用前,必须要进行显式赋值的!否则,编译不通过。
- 成员变量:在使用前,都经历过默认初始化赋值
类变量与局部变量的对比
参数表分配完毕之后,再根据方法体内定义的变量的顺序和作用域分配。
我们知道类变量表有两次初始化的机会,第一次是在“准备阶段”,执行系统初始化,对类变量设置零值,另一次则是在“初始化”阶段,赋予程序员在代码中定义的初始值。
和类变量初始化不同的是,局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用。
例如:
public void test() {
int i;
System.out.println(i);//报错,i变量没有初始化(i是局部变量)
== 这样的代码是错误的,没有赋值不能够使用。 ==
jvm中运行时数据区内部结构图,以及类加载的三个阶段,有助于理解上文。
类从被虚拟机加载到内存开始,直到卸载出内存为止,整个生命周期包括:加载——验证——准备——解析——初始化——使用——卸载 这7个阶段。其中验证、准备、解析3个部分统称为连接。
生命周期图如下:
因为类的加载分三个阶段:Loading、Linking、initialization
Loading:
加载是类加载的一个阶段,在加载阶段 虚拟机需要完成下面3件事情
- 通过类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化成方法区的运行时数据结构
- 在内存中生成一个代表此类的java.lang.Object对象,作为方法区这个类的各种数据的访问入口
相对于类加载的其他阶段,加载阶段(准确的说,是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的。因为加载阶段既可以使用系统提供的引导类加载器来完成,也可以由开发人员自定义的类加载器来完成(即重写类加载器的loadClass()方法)。
加载完成后,外部的二进制字节流就转化成虚拟机所需的格式存储在方法区中,然后在内存中实例化一个java.lang.Class类的对象。这个对象将作为程序访问方法区中的这些类型数据的外部接口。
加载阶段与连接阶段的部分内容是交叉进行的,并不是加载完成后才能执行验证等操作。这些夹在加载之中的动作仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。
Linking:
其中的第二个阶段Linking阶段(链接)又分三个:验证(verify)、准备(prepare)、解析(Resolve)
其中的准备(prepare)阶段描述:
为类变量分配内存并且设置该类变量的默认初始值,即零值。
这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化
这里不会为实例变量分配初始化,类变量会分配的方法区中,而实例变量会随着对象一起分配到java堆中。
initiallization:
- 初始化阶段就是执行类构造器方法()的过程
- 此方法不需要定义,是java编译器自动收集类中的所有变量的赋值动作和静态代码中的语句合并而来。
- 构造器方法中指令按语句在源文件中出现的顺序执行
- ()不同于类的构造器。(关联:构造器是虚拟机视角下的())
- 若该类具有父类,jvm会保证子类的()执行前,父类()已经执行完毕
- 虚拟机必须保证一个类的()方法在多线程下被同步加锁
代码例子:
运行结果:
补充说明
- 在栈帧中,与性能调优关系最为密切的部分就是局部变量表。在方法执行时,虚拟机使用局部变量表完成方法的传递。
- 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
推荐阅读