切换语言为:繁体

Java对象的创建过程详解

  • 爱糖宝
  • 2024-09-04
  • 2053
  • 0
  • 0

在Java中,对象创建是一个至关重要的过程,它涉及类加载、内存分配、初始化、赋值等多个阶段。理解这些过程不仅有助于编写更高效的代码,还能帮助我们优化程序的性能。本文将深入分析Java对象创建的全过程,重点讨论类加载、对象初始化、静态与非静态初始化的顺序,以及JVM堆栈内存的分配机制,并结合代码进行解析。

一、类加载过程

Java虚拟机(JVM)在执行Java程序时,需要首先将类加载到内存中。类加载分为以下几个步骤:

  1. 加载(Loading):JVM通过类加载器(ClassLoader)加载字节码文件(.class文件)到内存中,并将其转换为 Class 对象。

  2. 链接(Linking)

    • 验证(Verification):确保类的字节码符合JVM规范。

    • 准备(Preparation):为类的静态变量分配内存,并初始化为默认值。

    • 解析(Resolution):将常量池中的符号引用替换为直接引用。

  3. 初始化(Initialization):执行类的静态代码块以及静态变量的初始化。

示例代码:

public class InitializeDemo2 {  
    private static int k = 1;  
    private static int n = 10;  
    private static int i;  
    private String s = new String("-----");  
  
    static {  
        System.out.println("静态代码块:执行了");  
        System.out.println("k=" + k + ", n=" + n + ", i=" + i);  
    }  
  
    private int h = 2;  
  
    {  
        System.out.println("普通代码块:执行了");  
        System.out.println(s);  
    }  
  
    public static void m1() {  
        System.out.println("静态方法:执行了");  
        System.out.println("k=" + (k++) + ", n=" + n + ", i=" + i);  
    }  
  
    public void m2() {  
        System.out.println("普通方法:执行了");  
        System.out.println("k=" + (k++) + ", n=" + n + ", i=" + i);  
    }  
  
  
    public InitializeDemo2(String str) {  
        System.out.println("构造器:执行了");  
        System.out.println("k=" + (k++) + ", n=" + n + ", i=" + i);  
    }  
      
}

在上述代码中,InitializeDemo2 类的加载过程会按上述顺序进行。静态变量 k 在“准备”阶段被分配内存并初始化为默认值 0,在“初始化”阶段被赋值为 1

说到这里,就要暂停下。我们怎么验证类的加载过程呢(本文仅从程序员更容易看懂的角度)?我们知道,如果调用类内部的静态方法,会触发类加载过程。

通过在另一个类调用IInitializeDemo2 的静态方法:

public static void main(String args[]) {  
    InitializeDemo2.m1();  
}

可以验证到输出是:

静态代码块:执行了
k=1, n=10, i=0
静态方法:执行了
k=1, n=10, i=0

说明,静态变量在类加载阶段就已经完成了!

二、对象初始化

在对象创建过程中,核心的过程是初始化,其分为两个主要部分:静态初始化非静态初始化

  1. 静态初始化:只在类第一次加载时执行,用于初始化静态变量和执行静态代码块。

  2. 非静态初始化:每次创建对象时执行,用于初始化实例变量和执行非静态代码块。

初始化顺序:

  • 静态变量和静态代码块:按它们在类中出现的顺序执行,只在类加载时执行一次,在前面已经通过代码得到验证。

  • 实例变量和实例代码块:在每次创建对象时执行,执行顺序同样是按照它们在类中出现的顺序。

  • 构造函数:实例变量和实例代码块执行后,才会执行构造函数。

示例代码:

public class InitOrder {
    static {
        System.out.println("Static Block 1");
    }
    
    static int x = print("Static Variable x");
    
    static {
        System.out.println("Static Block 2");
    }
    
    int y = print("Instance Variable y");
    
    {
        System.out.println("Instance Block");
    }
    
    public InitOrder() {
        System.out.println("Constructor");
    }
    
    static int print(String message) {
        System.out.println(message);
        return 0;
    }
    
    public static void main(String[] args) {
        new InitOrder();
    }
}

输出结果:

Static Block 1
Static Variable x
Static Block 2
Instance Variable y
Instance Block
Constructor

从上面的例子中可以看出,静态块和静态变量按照声明顺序初始化(读者可以调整顺序验证),然后执行实例变量的初始化,接着执行实例块,最后调用构造函数。

三、JVM堆栈内存的分配

JVM在创建对象时,主要使用堆内存、栈内存进行分配:

  1. 堆内存:用于存储对象实例和数组。每当我们用 new 关键字创建一个对象时,都会在堆中分配内存,其实这是一个动态的过程。

  2. 栈内存:用于存储方法调用相关的信息,包括方法的参数、局部变量、操作数栈和返回地址。每次方法调用都会在栈中创建一个栈帧(Stack Frame)。

对象创建时的堆、栈内存分配过程:

  • new 关键字用于创建对象时,JVM首先会在堆中为该对象分配内存空间,并初始化默认值(如 int 默认值为 0,对象引用默认值为 null)。

  • 然后,JVM会将对象的引用地址存储在栈中的局部变量表中。

示例代码:

public class MemoryAllocation {
    public static void main(String[] args) {
        Example example = new Example();
    }
}

在这段代码中,new Example() 会在堆中分配 Example 对象的内存,并在栈中存储指向这个对象的引用 example。当方法执行结束,栈帧被销毁,但堆中的对象只要有引用指向它,就不会被垃圾回收。

对于内存分配,这里本文不计划深入探讨了,计划未来再写篇文章。

四、赋值过程

在Java中,对象的赋值过程包括两个主要部分:默认初始化显式初始化

  • 默认初始化:在对象内存分配后,JVM自动将对象的所有实例变量设置为其默认值。

  • 显式初始化:在默认初始化之后,Java按照代码中出现的顺序进行显式初始化。如果有构造函数,则在显式初始化完成后执行构造函数中的赋值操作。

示例代码:

public class ValueAssignment {
    int i = 5;
    String s = "Hello";
    
    public ValueAssignment() {
        i = 10;
        s = "World";
    }
    
    public static void main(String[] args) {
        ValueAssignment va = new ValueAssignment();
        System.out.println("i: " + va.i + ", s: " + va.s);
    }
}

输出结果:

i: 10, s: World

这里的过程如下:

  1. 默认初始化i 被设置为 0s 被设置为 null

  2. 显式初始化i 被赋值为 5s 被赋值为 "Hello"

  3. 构造函数初始化i 被重新赋值为 10s 被重新赋值为 "World"

结论

对象创建是Java程序中非常基础但复杂的过程,涉及类加载、静态与非静态初始化的顺序、内存分配和赋值等多个方面。理解这些细节对于编写高效、健壮的Java代码至关重要。通过本文的分析和示例代码,我们深入探讨了对象创建的每一个阶段以及它们在JVM中的具体实现方式,为深入掌握Java的内存模型和对象生命周期奠定了基础。

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.