Static Fields and Garbage Collection – 静态字段和垃圾回收

最后修改: 2022年 9月 7日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll learn how garbage collection treats static fields. Also, we’ll touch on such topics as class loading and class objects. After this article, we’ll understand better the connection between classes, classloaders, and static fields and how the garbage collector treats them.

在本教程中,我们将学习垃圾收集如何处理静态字段。此外,我们还将涉及类加载和类对象等主题。在这篇文章之后,我们将更好地理解类、类加载器和静态字段之间的联系,以及垃圾收集器如何对待它们。

2. Overview of Garbage Collection in Java

2.Java中的垃圾收集概述

Java provides a pretty nice feature of automatic memory management. This approach, in most cases, isn’t as efficient as the manual. However, it helps to avoid hard-to-debug problems and reduces boilerplate code. Also, with the improvements in garbage collection, the process becomes better and better. Thus, we should review how the garbage collector works and what garbage is in our applications.

Java提供了一个相当不错的自动内存管理功能。这种方法,在大多数情况下,并不像手动那样高效。然而,它有助于避免难以调试的问题,并减少模板代码。另外,随着垃圾收集的改进,这个过程变得越来越好。因此,我们应该回顾一下垃圾收集器是如何工作的,以及我们的应用程序中的垃圾是什么。

2.1. Garbage Objects

2.1.垃圾对象

Reference counting is the most straightforward and intuitive way to identify garbage objects. This approach allows us to check if our current object has any references to it. However, this approach has some drawbacks, and the most significant one is cyclic references.

引用计数是识别垃圾对象的最直接和最直观的方法。这种方法允许我们检查我们当前的对象是否有任何引用。然而,这种方法也有一些缺点,其中最主要的是循环引用。

One of the methods to deal with cyclic references is tracing. Objects turn into garbage when they don’t have any links to the garbage collection roots of the application.

处理循环引用的方法之一是追踪。当对象与应用程序的垃圾收集根没有任何链接时,它们就会变成垃圾。

2.2. Static Fields and Class Objects

2.2.静态字段和类对象

In Java, everything is an Object, including the definition of classes. They contain all the meta information about a class, methods, and the values of static fields. Thus, all the static fields are references by respected class objects. Therefore, until the class object exists and references by the application, static fields won’t be eligible for garbage collection.

在Java中,所有东西都是Object,包括类的定义。它们包含关于一个类的所有元信息、方法和static字段的值。因此,所有的static字段都是被尊重的class对象所引用。因此,在类对象存在并被应用程序引用之前,static字段没有资格进行垃圾收集

At the same time, all the loaded classes have the reference to the classloader used for loading this particular class. This way, we can keep track of the loaded classes.

同时,所有加载的类都有对用于加载这个特定类的类加载器的引用。这样一来,我们就可以跟踪已加载的类。

In this case, we have a hierarchy of references. A classloader keeps the reference to all the loaded classes. At the same time, classes store the reference to the respective classloader. We have two-way referencing in this case. Whenever we’re instantiating a new object, it will hold a reference to the definition of its classes. Thus, we have the following hierarchy:

在这种情况下,我们有一个引用的层次结构。一个classloader保留了对所有被加载的类的引用。同时,类存储了对各自classloader的引用。在这种情况下,我们有双向的引用。每当我们在实例化一个新的对象时,它将持有对其类的定义的引用。因此,我们有以下的层次结构。

classloader diagram

We cannot unload a class until our application has a reference to it. Let’s check what we need to make a class definition eligible for garbage collection. First, there should be no references from an application to the instances of a class. It’s important because all the instances bear references to their class. Second, the class loader for this class should be unavailable from an application. And lastly, the class itself should have no references in an application.

在我们的应用程序拥有对一个类的引用之前,我们无法卸载该类。让我们检查一下我们需要什么来使一个类的定义符合垃圾收集的条件。首先,不应该有来自应用程序对类的实例的引用。这很重要,因为所有的实例都有对其类的引用。其次,这个类的加载器应该从应用程序中不可用。最后,这个类本身在应用程序中不应该有任何引用。

3. Example of a Garbage Collected Static Field

3.垃圾收集的静态字段的例子

Let’s create an example in which we make a garbage collector remove our static field. JVM supports class unloading for the classes loaded by extension and system class loaders. However, this will be hard to reproduce, and we’ll use a custom class loader for this as we’ll have more control over it.

让我们创建一个例子,在这个例子中我们让垃圾收集器移除我们的静态字段。JVM支持由扩展类加载器和系统类加载器加载的类的卸载。然而,这将很难重现,我们将使用一个自定义的类加载器来实现,因为我们将对它有更多的控制。

3.1. Custom Class Loader

3.1.自定义类加载器

First, let’s create our own CustomClassloader will load a class from a resource folder of our application. For our classloader to work, we should override the loadClass(String name) method:

首先,让我们创建自己的CustomClassloader,从我们应用程序的资源文件夹中加载一个类。为了使我们的Classloader工作,我们应该覆盖loadClass(String name) 方法。

public class CustomClassloader extends ClassLoader {

    public static final String PREFIX = "com.baeldung.classloader";

    public CustomClassloader(ClassLoader parent) {
        super(parent);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.startsWith(PREFIX)) {
            return getClass(name);
        } else {
            return super.loadClass(name);
        }
    }

    ...
}

In this implementation, we’re using the getClass method, which hides the complexity of loading a class from resources:

在这个实现中,我们使用getClass方法,它隐藏了从资源中加载一个类的复杂性。

private Class<?> getClass(String name) {
    String fileName = name.replace('.', File.separatorChar) + ".class";
    try {
        byte[] byteArr = IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream(fileName));
        Class<?> c = defineClass(name, byteArr, 0, byteArr.length);
        resolveClass(c);
        return c;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3.2. Folder Structure

3.2.文件夹结构

To work correctly, our custom class should be outside the scope of our class path. This way, it won’t be uploaded by a system class loader. The only classloader working with this particular class will be our CustomClassloader.

为了正常工作,我们的自定义类应该在我们的类路径范围之外。这样,它就不会被系统类加载器上传。唯一与这个特定类合作的类加载器将是我们的CustomClassloader

The structure of our folders will look like this:

我们的文件夹的结构将看起来像这样。

Screen-Shot-2022-09-05-at-10.46.21

3.3. Static Field Holder

3.3.静态场支架

We’ll use a custom class that will play the role of a holder for our static field. After defining our implementation of a classloader, we can use it to upload a class we’ve prepared. It’s a simple class:

我们将使用一个自定义类,它将扮演我们的静态字段的持有者的角色。在定义了我们的类加载器的实现后,我们可以用它来上传我们准备的一个类。这是一个简单的类。

public class GarbageCollectedStaticFieldHolder {

    private static GarbageCollectedInnerObject garbageCollectedInnerObject =
      new GarbageCollectedInnerObject("Hello from a garbage collected static field");

    public void printValue() {
        System.out.println(garbageCollectedInnerObject.getMessage());
    }
}

3.4. Static Field Class

3.4.静态字段类

GarbageCollectedInnerObject will represent an object that we want to turn into garbage. This class, for simplicity and convenience defined in the same file as GarbageCollectedStaticFieldHolder. This class contains a message and also has overridden finalize() method. Although the finalize() method is deprecated and has many drawbacks, it will allow us to visualize when the garbage collector removes the object. We’ll use this method only for presentation purposes. Here’s our class for a static field:

GarbageCollectedInnerObject 将代表一个我们想变成垃圾的对象。这个类,为了简单方便,与GarbageCollectedStaticFieldHolder定义在同一个文件中。这个类包含一个消息,也有重写的finalize()方法。尽管finalize()方法已被废弃,并且有很多缺点,但它将使我们能够直观地看到垃圾收集器何时删除该对象。我们将只为演示目的使用这个方法。这里是我们的静态字段的类。

class GarbageCollectedInnerObject {

    private final String message;

    public GarbageCollectedInnerObject(final String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    @Override
    protected void finalize() {
        System.out.println("The object is garbage now");
    }
}

3.5. Uploading a Class

3.5.上传一个班级

Now we can upload and instantiate our class. After creating an instance, we can ensure that the class was uploaded, the object created, and the static field contains the needed information:

现在我们可以上传并实例化我们的类。在创建一个实例后,我们可以确保类被上传,对象被创建,静态字段包含所需的信息。

private static void loadClass() {
    try {
        final String className = "com.baeldung.classloader.GarbageCollectedStaticFieldHolder";
        CustomClassloader loader = new CustomClassloader(Main.class.getClassLoader());
        Class<?> clazz = loader.loadClass(className);
        Object instance = clazz.getConstructor().newInstance();
        clazz.getMethod(METHOD_NAME).invoke(instance);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

This method should create the instance of our special class and output the message:

这个方法应该创建我们的特殊类的实例并输出消息。

Hello from a garbage collected static field

3.6. Garbage Collection in Action

3.6.行动中的垃圾收集

Now let’s start our application and try to remove the garbage:

现在让我们启动我们的应用程序,并尝试删除垃圾。

public static void main(String[] args) throws InterruptedException {
    loadClass();
    System.gc();
    Thread.sleep(1000);
}

After calling the method loadClass(), all the variables inside this method, namely, classloader, our class, and instance, will go out of scope and lose the connection with the garbage collection roots. It’s also possible to assign null to the references, but the option to use scope is cleaner:

在调用loadClass()方法后,该方法中的所有变量,即classloader、我们的类和实例,都将超出范围,并失去与垃圾收集根的联系。也可以将null分配给引用,但使用范围的选项更干净。

public static void main(String[] args) throws InterruptedException {
    CustomClassloader loader;
    Class<?> clazz;
    Object instance;
    try {
        final String className = "com.baeldung.classloader.GarbageCollectedStaticFieldHolder";
        loader = new CustomClassloader(GarbageCollectionNullExample.class.getClassLoader());
        clazz = loader.loadClass(className);
        instance = clazz.getConstructor().newInstance();
        clazz.getMethod(METHOD_NAME).invoke(instance);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    loader = null;
    clazz = null;
    instance = null;
    System.gc();
    Thread.sleep(1000);
}

Even though we have some problems with this code, it’ll work in most cases. The main issue is that we cannot force garbage collection in Java, and the invocation of  System.gc() won’t guarantee that the garage collection will happen. However, in most JVM implementations, this will trigger Major Garbage Collection. Thus, we should see the following lines in the output:

尽管我们的这段代码有一些问题,但在大多数情况下,它都能发挥作用。主要问题是我们不能在Java中强制进行垃圾收集,调用System.gc()不会保证垃圾收集的发生。然而,在大多数JVM实现中,这将触发Major Garbage Collection。因此,我们应该在输出中看到以下几行。

Hello from a garbage collected static field 
The object is garbage now

This output shows us that the garbage collector removed the static field. The garbage collector also removed the classloader, the classes for the holder, the static field, and the connected objects.

这个输出告诉我们,垃圾收集器删除了静态字段。垃圾收集器还删除了classloader、持有人的类、静态字段和连接对象。

3.7. Example Without System.gc()

3.7.没有System.gc()的例子

We also can trigger garbage collection more naturally. This way will work more stable. However, it will require more cycles to invoke the garbage collector:

我们也可以更自然地触发垃圾回收。这种方式工作起来会更稳定。然而,它将需要更多的周期来调用垃圾收集器。

public static void main(String[] args) {
    while (true) {
        loadClass();
    }
}

Here we’re using the same loadClass() method, but we don’t invoke System.gc(), and the garbage collector is triggered when we run out of memory because we’re loading the class in an infinite loop.

这里我们使用了同样的loadClass()方法,但我们没有调用System.gc(),当我们的内存耗尽时,垃圾收集器就会被触发,因为我们在无限循环中加载类。

4. Conclusion

4.总结

This article taught us how garbage collection works in Java regarding classes and static fields. We created a custom class loader and used it for our example. Also, we learned the connection between classloaders, classes, and their static fields. For further understanding, it’s worth going through the articles linked in the texts.

这篇文章告诉我们在Java中关于类和静态字段的垃圾收集是如何工作的。我们创建了一个自定义的类加载器并将其用于我们的例子。此外,我们还学习了类加载器、类和其静态字段之间的联系。为了进一步了解,值得去看一下文中链接的文章。

The code is available over on GitHub.

该代码可在GitHub上获得。