在Java 9中,如果ClassLoader里有Cleaner管理的静态资源,如何卸载它们

问答在Java 9中,如果ClassLoader里有Cleaner管理的静态资源,如何卸载它们
韩圣妍 管理员 asked 2 年 ago
3 个回答
尹明烟 管理员 answered 2 年 ago

背景

Java 9引入了Cleaner机制,它可以自动释放由类加载器管理的静态资源。当一个类被卸载时,它的类加载器也会被卸载,而Cleaner机制会负责释放该类加载器持有的所有资源。

问题

如果一个类加载器持有由Cleaner管理的静态资源,如何卸载它们?

解决方案

要卸载由Cleaner管理的静态资源,我们需要执行以下步骤:

  1. 获取Cleaner引用:可以通过调用java.lang.ref.Cleaner.create()方法来获取Cleaner引用。该方法接收一个需要释放的资源和一个Runnable作为参数。Runnable将在资源被释放时执行。
  2. 向Cleaner注册资源:使用Cleaner引用调用Cleaner.register()方法将需要释放的资源注册到Cleaner中。
  3. 卸载类加载器:当不再需要类加载器时,可以调用ClassLoader.unload()方法将其卸载。
  4. 释放资源:当类加载器被卸载后,Cleaner注册的资源将被释放。

示例代码

以下示例代码演示了如何卸载由Cleaner管理的静态资源:

“`java
import java.lang.ref.Cleaner;

public class CleanerExample {

private static final Cleaner cleaner = Cleaner.create();
public static void main(String[] args) {
    // 创建一个静态资源
    byte[] data = new byte[1024];
    // 向Cleaner注册资源
    cleaner.register(data, () -> {
        // 在资源被释放时执行的代码
        System.out.println("释放资源");
    });
    // 卸载类加载器
    ClassLoader classLoader = CleanerExample.class.getClassLoader();
    classLoader.unload();
    // 资源已释放
}

}
“`

深入解释

Cleaner.create()方法返回一个Cleaner引用,该引用可用于注册资源并管理它们的释放。Cleaner.register()方法接收两个参数:需要释放的资源和一个Runnable。Runnable将在资源被释放时执行。

当类加载器被卸载时,它持有的Cleaner引用也会被注销。此时,Cleaner将释放它注册的所有资源,并执行相应的Runnable。

通过使用Cleaner机制,我们可以确保即使类加载器被卸载,由其管理的静态资源也会被正确释放。这对于避免内存泄漏和改善资源管理至关重要。

注意事项

使用Cleaner机制时,需要注意以下几点:

  • Cleaner只能释放本地资源,例如直接内存块或文件句柄
  • Cleaner不能释放由其他类加载器管理的资源。
  • Cleaner不能释放对象引用。
胡柏艺 管理员 answered 2 年 ago

在Java 9中,引入了Cleaner机制来管理静态资源的释放,从而增强了资源管理。当一个ClassLoader包含由Cleaner管理的静态资源时,在卸载ClassLoader时,这些静态资源可能会保持活动状态,从而导致内存泄漏。为了解决这个问题,Java 9提供了以下方法:

1. 使用PhantomReference

PhantomReference是一种弱引用,当它引用的对象无法访问时,将被垃圾回收。我们可以创建一个PhantomReference,指向由Cleaner管理的静态资源,然后在卸载ClassLoader时通过它来清除资源。

“`java
public static void main(String[] args) {
// 创建一个ClassLoader
ClassLoader classLoader = new URLClassLoader(new URL[0], ClassLoader.getSystemClassLoader());

// 创建一个Cleaner,并将其附加到一个静态资源
Cleaner cleaner = Cleaner.create(classLoader, () -> {
    // 释放静态资源
});
// 创建一个PhantomReference,指向静态资源
PhantomReference<Object> phantomReference = new PhantomReference<>(resource, cleaner);
// 卸载ClassLoader
classLoader = null; // 触发PhantomReference的GC
// 在PhantomReference被GC后,Cleaner会被执行,释放静态资源

}
“`

2. 使用Finalizer

Finalizer是一种特殊的Java方法,当一个对象被GC时,JVM会调用它。我们可以创建Finalizer,在其中释放由Cleaner管理的静态资源。

“`java
public static void main(String[] args) {
// 创建一个ClassLoader
ClassLoader classLoader = new URLClassLoader(new URL[0], ClassLoader.getSystemClassLoader());

// 创建一个Cleaner,并将其附加到一个静态资源
Cleaner cleaner = Cleaner.create(classLoader, () -> {
    // 释放静态资源
});
// 将Cleaner附加到一个对象上,使其成为finalizable
Object finalizableObject = new Object() {
    @Override
    protected void finalize() {
        cleaner.clean();
    }
};
// 卸载ClassLoader
classLoader = null; // 触发Finalizer的GC
// 在finalizableObject被GC后,Cleaner会被执行,释放静态资源

}
“`

3. 使用WeakReference

WeakReference是一种弱引用,当它引用的对象无法访问时,将被垃圾回收。与PhantomReference不同,WeakReference在对象被GC之前保持对它的引用。我们可以使用WeakReference来存储Cleaner,并在卸载ClassLoader时通过它来清除资源。

“`java
public static void main(String[] args) {
// 创建一个ClassLoader
ClassLoader classLoader = new URLClassLoader(new URL[0], ClassLoader.getSystemClassLoader());

// 创建一个Cleaner,并将其附加到一个静态资源
Cleaner cleaner = Cleaner.create(classLoader, () -> {
    // 释放静态资源
});
// 创建一个WeakReference,指向Cleaner
WeakReference<Cleaner> cleanerReference = new WeakReference<>(cleaner);
// 卸载ClassLoader
classLoader = null; // 触发WeakReference的GC
// 在WeakReference被GC后,Cleaner会被使用,释放静态资源

}
“`

总之,在Java 9中,如果ClassLoader里有Cleaner管理的静态资源,我们可以使用PhantomReference、Finalizer或WeakReference来卸载它们,从而防止内存泄漏。

孟辰思 管理员 answered 2 年 ago

Java 9引入了Cleaner类,作为垃圾回收机制的重要补充,用于管理静态资源的卸载。静态资源是指在类加载时分配的资源,如文件句柄、本地内存或其他非Java对象。Cleaner负责在这些资源不再需要时释放它们,即使对应的类已被卸载。

当ClassLoader加载一个类时,它会创建一个Cleaner引用,该引用指向该类加载的非Java资源。当ClassLoader被卸载时,Java虚拟机(JVM)会自动调用Cleaner的close方法,从而释放这些资源。

然而,在某些情况下,JVM可能无法自动卸载ClassLoader。例如,当类加载器直接被引用或通过静态成员变量被隐式引用时。此时,Cleaner管理的静态资源将无法被释放,从而导致内存泄漏。

为了解决这个问题,Java 9提供了一个明确释放Cleaner管理资源的机制:

1. 使用WeakReference持有ClassLoader

WeakReference是一种弱引用,当垃圾收集器遇到它所引用的对象时,不会阻止该对象的回收。因此,我们可以使用WeakReference来持有ClassLoader,这样当ClassLoader不再被其他对象引用时,它就会被回收,从而触发Cleaner的close方法。

“`java
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

public class ClassLoaderUnloader {
private static final WeakHashMap cleaners = new WeakHashMap<>();

public static void register(ClassLoader classLoader, Cleaner cleaner) {
    cleaners.put(classLoader, cleaner);
}
public static void unload(ClassLoader classLoader) {
    Cleaner cleaner = cleaners.remove(classLoader);
    if (cleaner != null) {
        cleaner.clean();
    }
}

}
“`

2. 使用PhantomReference持有ClassLoader

PhantomReference是一种更弱的引用,只有当JVM准备对引用对象进行垃圾回收时,它才会被通知。这意味着我们可以使用PhantomReference来跟踪ClassLoader,直到它被JVM回收为止,然后触发Cleaner的close方法。

“`java
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.Map;
import java.util.WeakHashMap;

public class ClassLoaderUnloader {
private static final ReferenceQueue queue = new ReferenceQueue<>();
private static final Map cleaners = new WeakHashMap<>();

public static void register(ClassLoader classLoader, Cleaner cleaner) {
    cleaners.put(classLoader, cleaner);
}
public static void unload() {
    ClassLoader classLoader;
    while ((classLoader = (ClassLoader) queue.poll()) != null) {
        Cleaner cleaner = cleaners.remove(classLoader);
        if (cleaner != null) {
            cleaner.clean();
        }
    }
}

}
“`

3. 使用Cleaner.create()方法

Java 9还引入了Cleaner.create()方法,它允许我们直接创建一个Cleaner引用,而无需通过ClassLoader。我们可以使用Cleaner.create()方法来管理静态资源,并在不再需要时显式释放它们。

“`java
import java.lang.ref.Cleaner;

public class ResourceUnloader {
private static final Cleaner cleaner = Cleaner.create();

public static void register(Object resource) {
    cleaner.register(resource, () -> {
        // 释放resource
    });
}
public static void unload() {
    cleaner.clean();
}

}
“`

通过以上方法,我们可以卸载ClassLoader中由Cleaner管理的静态资源,从而防止内存泄漏。需要注意的是,在使用上述方法时,需要确保Cleaner引用不会被其他对象持有,否则可能导致相反的结果。

公众号