1. 概述

本文将深入探讨垃圾回收机制如何处理静态字段,同时涉及类加载类对象等核心概念。通过阅读,你将更清晰地理解类、类加载器与静态字段之间的关联,以及垃圾回收器对它们的处理方式。

2. Java垃圾回收机制概览

Java提供了自动内存管理这一强大特性。虽然这种方式在多数情况下不如手动管理高效,但它能有效避免难以调试的内存问题并减少样板代码。随着垃圾回收技术的持续优化,其性能也在不断提升。下面我们回顾垃圾回收器的工作原理及应用程序中的垃圾判定标准。

2.1 垃圾对象判定

引用计数是最直观的垃圾对象识别方式。通过检查对象是否存在引用即可判断其是否可回收。但这种方法存在明显缺陷,最严重的问题是循环引用

追踪算法是解决循环引用问题的有效方案。当对象与垃圾回收根节点失去所有连接时,即成为可回收的垃圾对象。

2.2 静态字段与类对象

在Java中,万物皆对象,包括类定义本身。类对象包含类的元信息、方法以及静态字段的值。因此,所有静态字段都由对应的类对象持有引用。这意味着:只要类对象存在且被应用引用,其静态字段就不会被垃圾回收

同时,所有已加载的类都持有加载该类的类加载器引用,形成双向引用关系:

  • 类加载器持有所有已加载类的引用
  • 类对象持有对应类加载器的引用

当创建新对象实例时,该实例会持有其类定义的引用,形成如下引用层级:

对象实例 → 类对象 → 类加载器

应用对类定义存在引用时,类无法被卸载。要使类定义可被垃圾回收,需满足三个条件:

  1. 应用中不存在该类的实例引用
  2. 该类的类加载器在应用中不可达
  3. 应用中不存在对该类的直接引用

3. 静态字段被垃圾回收的示例

下面我们通过示例演示垃圾回收器如何移除静态字段。虽然JVM支持对扩展类加载器和系统类加载器加载的类进行卸载,但难以复现。因此我们将使用自定义类加载器以获得更好的控制效果。

3.1 自定义类加载器

创建CustomClassloader从应用资源文件夹加载类。需重写*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);
        }
    }

    ...
}

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 文件夹结构

为正确运行,自定义类需位于类路径范围之外。这样可避免被系统类加载器加载,确保只有我们的CustomClassloader能处理该类。

文件夹结构如下:

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── baeldung/
│   │           └── classloader/
│   │               ├── CustomClassloader.java
│   │               └── Main.java
│   └── resources/
│       └── com/
│           └── baeldung/
│               └── classloader/
│                   ├── GarbageCollectedStaticFieldHolder.class
│                   └── GarbageCollectedInnerObject.class

3.3 静态字段持有者

创建GarbageCollectedStaticFieldHolder类作为静态字段容器:

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 静态字段类

GarbageCollectedInnerObject代表我们希望回收的对象。为简化演示,将其定义在GarbageCollectedStaticFieldHolder同一文件中。该类包含消息字段并重写了*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");
    }
}

注意:虽然*finalize()*方法已废弃且存在诸多缺陷,但这里仅用于演示垃圾回收时机。

3.5 加载类

使用自定义类加载器加载并实例化类:

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);
    }
}

执行后将输出:

Hello from a garbage collected static field

3.6 垃圾回收实战

启动应用并尝试触发垃圾回收:

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

*loadClass()*方法执行后,所有局部变量(类加载器、类对象、实例)将超出作用域,与垃圾回收根节点断开连接。也可显式置空引用:

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);
}

踩坑提示:Java中无法强制执行垃圾回收,System.gc()调用不保证立即回收。但在多数JVM实现中,会触发Major GC

预期输出:

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

这表明垃圾回收器成功移除了静态字段,同时回收了类加载器、容器类、静态字段及其关联对象。

3.7 不使用System.gc()的示例

更自然的垃圾回收触发方式,虽然需要更多执行周期但更稳定:

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

通过无限循环加载类,当内存耗尽时垃圾回收器会自动触发。

4. 总结

本文深入探讨了Java中类和静态字段的垃圾回收机制,通过自定义类加载器示例演示了回收过程。关键要点包括:

  • 静态字段的生命周期与类对象绑定
  • 类卸载需满足三个严格条件
  • 自定义类加载器可精确控制类加载与卸载

完整代码示例可在GitHub获取。建议结合文中提到的类加载器和垃圾回收相关文章进一步学习。


原始标题:Static Fields and Garbage Collection