1. 概述
本文将深入探讨垃圾回收机制如何处理静态字段,同时涉及类加载和类对象等核心概念。通过阅读,你将更清晰地理解类、类加载器与静态字段之间的关联,以及垃圾回收器对它们的处理方式。
2. Java垃圾回收机制概览
Java提供了自动内存管理这一强大特性。虽然这种方式在多数情况下不如手动管理高效,但它能有效避免难以调试的内存问题并减少样板代码。随着垃圾回收技术的持续优化,其性能也在不断提升。下面我们回顾垃圾回收器的工作原理及应用程序中的垃圾判定标准。
2.1 垃圾对象判定
引用计数是最直观的垃圾对象识别方式。通过检查对象是否存在引用即可判断其是否可回收。但这种方法存在明显缺陷,最严重的问题是循环引用。
追踪算法是解决循环引用问题的有效方案。当对象与垃圾回收根节点失去所有连接时,即成为可回收的垃圾对象。
2.2 静态字段与类对象
在Java中,万物皆对象,包括类定义本身。类对象包含类的元信息、方法以及静态字段的值。因此,所有静态字段都由对应的类对象持有引用。这意味着:只要类对象存在且被应用引用,其静态字段就不会被垃圾回收。
同时,所有已加载的类都持有加载该类的类加载器引用,形成双向引用关系:
- 类加载器持有所有已加载类的引用
- 类对象持有对应类加载器的引用
当创建新对象实例时,该实例会持有其类定义的引用,形成如下引用层级:
对象实例 → 类对象 → 类加载器
应用对类定义存在引用时,类无法被卸载。要使类定义可被垃圾回收,需满足三个条件:
- 应用中不存在该类的实例引用
- 该类的类加载器在应用中不可达
- 应用中不存在对该类的直接引用
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获取。建议结合文中提到的类加载器和垃圾回收相关文章进一步学习。