对象的实例化过程
- 如图是JVM的完整框图
- [1] :是类加载阶段,类加载的过程一共有三个阶段:(1)加载阶段、(2)链接阶段、(3)初始化阶段。加载 .class文件时,采用的是双亲委派机制。
- (1)加载阶段: 通过该类的全限定名拿到该类的二进制字节流,之后将该字节流所代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
- (2)链接阶段分为三步:验证、准备、解析。
- 验证(Verify):文件格式验证,元数据验证,字节码验证,符号引用验证。例如字节码文件,其开头均为CAFE BABE。
- 准备(Prepare):为类(static)变量分配内存并且设置该类变量的默认初始值,即零值;不包含用final修饰的static,因为final在编译的时候就会分配好了默认值,准备阶段会显式初始化;不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
- 解析(Resolve):将常量池内的符号引用转换为直接引用的过程;事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行;符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄;解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref info等。
- (3)初始化阶段:为静态变量赋值,执行static代码块。初始化阶段就是执行类构造器方法
<clinit>()
的过程,当我们代码中包含static变量的时候,就会有clinit方法,构造器方法中指令按语句在源文件中出现的顺序执行
/**
* 例子如下:静态变量number值的变化过程为:number = 0 --> initial: 10 --> 15
* 1:链接linking的prepare这个过程:为类(static)变量分配内存并且设置该类变量的默认初始值 number = 0
* 2:初始化:顺序执行 number = 0 --> initial: 10 --> 15
*/
public class ClassTest {
//@初始化阶段 先执行number = 10
private static int number = 10;
static {
//再执行number = 15 顺序执行
number = 15;
}
public static void main(String[] args) {
//打印为15
System.out.println(ClassTest.number);
}
}
对象的创建过程
-
对象创建的方式
-
创建对象的步骤
//[方法区] [栈:局部变量表] = [堆]
Object obj = new Object();
-
给对象属性赋值的顺序为:
-
属性的默认值初始化
-
显示初始化/代码块初始化(并列关系,谁先谁后看代码编写的顺序)
-
构造器初始化
-
class InitTest {
String name;
//1. 属性的默认值初始化
int age = 1001;
Student stu;
{
//2. 显示初始化/代码块初始化(并列关系,谁先谁后看代码编写的顺序)
name = "张三";
}
public InitTest(){
//3. 构造器初始化
stu = new Student();
}
}
class Student {}
对象的访问方式
- 句柄访问
- 缺点:在堆空间中开辟了一块空间作为句柄池,句柄池本身也会占用空间;通过两次指针访问才能访问到堆中的对象,效率低。
- 优点:reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference本身不需要被修改。
- 直接指针(HotSpot采用)
- 优点:直接指针是局部变量表中的引用,直接指向堆中的实例,在对象实例中有类型指针,指向的是方法区中的对象类型数据。
- 缺点:对象被移动(垃圾收集时移动对象很普遍)时需要修改 reference 的值。