你好!

我想如果我告诉您您的计算机的内存量有限,您不会太惊讶 :) 即使是硬盘驱动器——通常比 RAM 存储容量大很多倍——也可以装满你最喜欢的游戏、电视节目、和更多。为防止这种情况发生,您需要监控内存的当前状态并从计算机中删除不需要的文件。Java 编程与这一切有什么关系?一切!毕竟,当 Java 机器创建任何对象时,它都会为该对象分配内存。

在一个真正的大程序中,创建了数以万计的对象,每个对象都有自己的一块内存分配给它。但是你认为所有这些物体存在多久了?他们在我们的程序运行的整个过程中都“活着”吗?当然不是。即使具有 Java 对象的所有优点,它们也不是不朽的 :) 对象有自己的生命周期。今天我们将暂停编写代码,看看这个过程 :) 此外,它对于您理解程序的工作原理和资源的管理方式非常重要。那么,对象的生命从什么时候开始呢?就像一个人——从它的诞生,即创造。


Cat cat = new Cat(); // Here the lifecycle of our Cat object begins!

首先,Java 虚拟机分配所需的内存量来创建对象。然后它创建对该内存的引用。在我们的例子中,该引用称为cat,因此我们可以跟踪它。然后它的所有变量都被初始化,构造函数被调用,然后——哒哒!— 我们新造的对象过着自己的生活 :)

对象的寿命各不相同,因此我们无法在此处提供确切的数字。在任何情况下,它都会在程序中存在一段时间并执行其功能。准确地说,只要有对它的引用,一个对象就是“活着”的。一旦没有剩余的引用,对象就会“死亡”。例子:


public class Car {
  
   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");
       lamborghini = null;

   }

}

Lamborghini Diablo 汽车对象在该方法的第二行停止活动main()。只有一个对它的引用,然后该引用被设置为等于null。由于没有对 Lamborghini Diablo 的剩余引用,分配的内存就变成了“垃圾”。不必将引用设置为 null 即可发生这种情况:


public class Car {

   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");

       Car lamborghiniGallardo = new Car("Lamborghini Gallardo");
       lamborghini = lamborghiniGallardo;
   }

}

这里我们创建了第二个对象,然后将这个新对象分配给引用lamborghini。现在Lamborghini Gallardo对象有两个引用,但Lamborghini Diablo对象没有。这意味着该Diablo对象现在是垃圾。这就是 Java 的内置机制,称为垃圾收集器 (GC) 发挥作用的时候。

垃圾收集器是一种内部 Java 机制,负责释放内存,即从内存中删除不需要的对象。我们在这里选择机器人真空吸尘器的图片是有充分理由的。毕竟,垃圾收集器的工作方式大致相同:在后台,它围绕您的程序“旅行”,几乎不需要您付出任何努力即可收集垃圾。它的工作是删除程序中不再使用的对象。

这样做可以为其他对象释放计算机内存。还记得我们在课程开始时说过,在日常生活中,您必须监控计算机的状态并删除不需要的文件吗?好吧,对于 Java 对象,垃圾收集器会为您完成这项工作。垃圾收集器在您的程序运行时重复运行:您不必显式调用它或给它命令,尽管这在技术上是可行的。稍后我们将更多地讨论它并更详细地分析它的工作。

当垃圾收集器到达一个对象时,就在销毁该对象之前,它会调用finalize()该对象的一个​​特殊方法——。该方法可以释放该对象使用的其他资源。该finalize()方法是类的一部分Object。这意味着除了您之前遇到的equals(),hashCode()和方法之外,每个对象都有这个方法。toString()它与其他方法的不同之处在于它——我该怎么说呢——非常反复无常。

特别是,它并不总是在对象销毁之前调用。编程是一项精确的工作。程序员告诉计算机做某事,计算机就会去做。我想你已经习惯了这种行为,所以一开始你可能很难接受下面这个想法:“在销毁对象之前,调用了类的方法。finalize()或者Object可能没有调用。这完全取决于你的运气!”

然而,这是真的。finalize()Java 机器根据具体情况自行决定是否调用该方法。例如,让我们尝试运行以下代码作为实验:


public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public Cat() {
   }

   public static void main(String[] args) throws Throwable {
       for (int i = 0 ; i < 1000000; i++) {
           Cat cat = new Cat();
           cat = null; // This is when the first object becomes available to the garbage collector
       }
   }

   @Override
   protected void finalize() throws Throwable {
       System.out.println("Cat object destroyed!");
   }
}

我们创建一个Cat对象,然后在下一行代码中将其唯一引用设置为 null。我们这样做了一百万次。我们明确地覆盖了该finalize()方法,以便它向控制台打印一个字符串一百万次(每次销毁一个Cat对象时打印一次)。但不是!准确地说,它在我的电脑上只运行了 37,346 次!也就是说,安装在我机器上的 Java 机器每 27 次中只有一次决定调用该finalize()方法。

在其他情况下,垃圾收集在没有它的情况下发生。尝试自己运行这段代码:您很可能会得到不同的结果。如您所见,finalize()很难称为可靠的合作伙伴 :) 所以,对未来的一点建议:不要依赖finalize()释放关键资源的方法。可能 JVM 会调用它,也可能不会。谁知道?

如果当您的对象处于活动状态时,它持有一些对性能非常重要的资源,例如,一个打开的数据库连接,最好在您的类中创建一个特殊的方法来释放它们,然后在对象不再存在时显式调用它需要。这样您就可以确定您的程序的性能不会受到影响。从一开始,我们就说处理内存和清除垃圾非常重要,这是事实。不正确地处理资源和误解如何清理不必要的对象会导致内存泄漏。这是最著名的编程错误之一。

如果程序员编写的代码不正确,每次都可能为新创建的对象分配新内存,而旧的、不需要的对象可能无法被垃圾收集器移除。由于我们用机器人真空吸尘器做了类比,想象一下如果在启动机器人之前,您将袜子撒在房子周围,打碎玻璃花瓶,并在地板上留下乐高积木,会发生什么情况。当然,机器人会尝试完成它的工作,但有时它会卡住。

为了让机器人吸尘器正常工作,您需要保持地板状况良好,并清除机器人无法处理的任何东西。同样的原则也适用于 Java 的垃圾收集器。如果程序中有许多无法清理的对象(如袜子或机器人真空吸尘器的乐高积木),有时您会耗尽内存。可能不仅仅是您的程序会冻结——计算机上运行的所有其他程序都可能受到影响。他们也可能没有足够的内存。

你不需要记住这个。您只需要了解其工作原理。