Java中的对象——生命周期详解

1. 对象的创建

1.1 使用 new 关键字

执行过程:当使用 new 关键字创建对象时,JVM 会为新对象在堆内存中分配一块空间,并调用对应的构造器来初始化对象。

示例代码

MyClass obj = new MyClass();

内存变化:JVM 在堆内存中分配空间,所有的成员变量会被初始化为默认值(如整数为 0,引用类型为 null)。构造器会被调用以执行任何自定义初始化逻辑。

关键操作

  • 构造器调用:如果 MyClass 有构造器,JVM 会执行这个构造器来设置对象的初始状态。

特点:这种方式是最常用的对象创建方式,类型在编译期已确定,确保了编译时的类型安全。

1.2 反射

执行过程:使用反射可以在运行时创建对象。反射机制通过类的 Class 对象来获取构造器并实例化对象。

示例代码

MyClass obj = MyClass.class.getDeclaredConstructor().newInstance();

内存变化:与 new 关键字相同,JVM 在堆内存中为新对象分配空间,构造器被调用来初始化对象。

关键操作

  • 无参构造器调用:使用 newInstance() 方法时,要求类中有无参构造器,否则会抛出异常。

特点:反射提供了更大的灵活性,能够在运行时创建对象,适合框架或需要动态实例化的场景,但性能稍差,并且缺乏编译时类型安全。

1.3 克隆

执行过程:使用 clone() 方法可以复制一个已有对象的状态,创建一个新的副本。为此,类必须实现 Cloneable 接口。

示例代码

​​​​​​​MyClass obj2 = (MyClass) obj1.clone();

 

内存变化:JVM 在堆内存中为新对象分配空间,所有简单字段(如整数)会按值复制,而引用字段(如对象引用)将复制指向原对象的引用。

关键操作

  • 浅拷贝:对于简单类型,字段值直接复制;对于引用类型,复制的是引用,意味着两个对象共享同一内存地址。

特点:克隆适合于需要对象副本的场景,但可能导致修改共享字段时的意外行为。使用时需谨慎处理引用类型字段。

1.4 反序列化

执行过程:通过从字节流中还原对象来创建实例。对象必须实现 Serializable 接口,以便能够序列化和反序列化。

示例代码

ObjectInputStream in = new ObjectInputStream(new FileInputStream("file.obj")); MyClass obj = (MyClass) in.readObject();

内存变化:JVM 在堆内存中分配空间,并将存储在字节流中的对象状态还原至该空间。

关键操作

  • 序列化和反序列化:在对象被序列化时,其状态被转换为字节流,反序列化时则从字节流中恢复原状态。

特点:适用于数据持久化或网络传输,能够保存和恢复对象的状态,但需要确保类的兼容性。

2. 对象的使用

使用过程:一旦对象被创建,可以通过其引用来访问成员变量或调用方法。这一阶段不同创建方式的对象在使用上没有显著区别。

示例代码

obj.fieldName = 10; 
// 修改字段值 
System.out.println(obj.fieldName); 
// 访问字段值 
obj.someMethod(); 
// 调用方法

变化情况:修改字段值时,内存中实际存储的内容会发生变化,但对象本身的存储位置保持不变。方法调用可能会改变对象的状态。

特点:所有通过不同方式创建的对象在这一阶段都可以直接进行操作,方法调用可以实现特定功能,灵活多样。

3. 变为不可达

变为不可达的过程:对象在没有任何引用指向它时,便变为不可达,等待垃圾回收。这可以通过多种方式实现。

3.1 引用置空

obj = null; // 解除对对象的引用

变化情况:此时对象不再被任何变量引用,成为不可达对象,等待垃圾回收。

3.2 超出作用域

void someMethod() { 
    MyClass obj = new MyClass(); 
    // obj是局部变量 
} 
// obj在方法结束后不可达

变化情况:当方法执行结束,局部变量obj超出作用域,对象也随之变为不可达。

3.3 循环引用​​​​​​​

MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass(); 
obj1.next = obj2; // obj1引用obj2 
obj2.next = obj1; // obj2引用obj1

变化情况:即使存在相互引用,如果没有外部引用,JVM会识别并回收这两个对象。

特点:所有这些方式都导致对象进入不可达状态,准备等待垃圾回收。

4. 垃圾回收

过程:一旦对象变为不可达,JVM的垃圾回收器会在合适的时候回收其占用的内存,确保有效的内存管理。

4.1 标记

在垃圾回收阶段,JVM会从根对象开始遍历,标记所有可达对象。

4.2 清除

清除阶段会释放未被标记的对象所占用的内存,确保垃圾对象被正确回收。

4.3 压缩

JVM可能会在清除阶段后移动存活对象,以避免内存碎片,确保内存的高效利用。

特点:垃圾回收是自动管理的,用户无需手动释放内存,大大减轻了内存管理的负担。

5. 对象的销毁

当对象被垃圾回收后,JVM会释放其占用的内存。销毁对象时可以执行一些清理操作。

5.1 finalize() 方法

示例代码

protected void finalize() { 
    System.out.println("Object is being destroyed"); 
}

变化情况finalize() 方法会在对象被回收前调用,但不保证一定会执行,可能导致资源未被及时释放。

特点:由于其不确定性,不推荐在现代Java中使用,可能影响性能。

5.2 AutoCloseable 接口

示例代码

try (MyClass obj = new MyClass()) { 
    obj.doSomething(); 
} // 自动调用 obj.close() 方法

变化情况:在资源使用结束后,自动调用 close() 方法,确保及时释放资源。

特点:这是管理资源(如文件、数据库连接)的一种更好方式,能够明确地控制资源释放的时机。

对比总结

  1. 对象创建

    • 方式
      • new关键字:最常用的创建方式,确保编译期的类型安全。
      • 反射:动态创建对象,适合框架,灵活但性能略低。
      • 克隆:复制已有对象,适用于需要保留状态的场景,但仅适用于简单字段。
      • 反序列化:从字节流恢复对象,适合持久化和网络传输,但要求类兼容性。
    • 特点:每种创建方式都在堆内存中分配空间并调用构造器,适用场景和灵活性不同。
  2. 对象使用

    • 对象在使用阶段通过引用访问成员变量和调用方法。所有创建方式的对象在使用上没有显著差异,均可以进行字段操作和方法调用。
  3. 变为不可达

    • 对象进入不可达状态时,无论是通过引用置空、超出作用域,还是循环引用,都会使得对象等待垃圾回收。所有这些方式的结果是相同的,即对象失去引用。
  4. 垃圾回收

    • 垃圾回收是自动进行的,包括标记、清除和压缩过程。所有不可达对象都将被回收,确保内存的有效利用和自动管理,用户无需手动干预。
  5. 对象销毁

    • 销毁过程主要涉及内存释放:
      • finalize():在对象被回收前调用,执行不确定,不推荐使用。
      • AutoCloseable:现代Java中优先采用的资源管理方式,确保及时释放资源,适合文件和数据库连接等需要手动关闭的资源。
    • 特点:销毁机制确保了在对象生命周期结束后,相关资源得到妥善管理,避免内存泄漏。​​​​​​​

        Java的对象生命周期管理机制通过自动垃圾回收和灵活的创建方式,使得开发者能够专注于业务逻辑,而无需过多担心内存管理的复杂性。不同的创建和使用方式提供了灵活性与选择,使得Java在开发大规模应用时具备了高效和安全的内存管理特性。在现代Java中,使用AutoCloseable接口进行资源管理已成为最佳实践,确保了资源的及时释放和更好的内存使用效率。

 

 

 

 

 

上一篇:当遇到 502 错误(Bad Gateway)怎么办


下一篇:江协科技STM32学习- P21 ADC模数转换器