在 Java 中,由 NIO 直接缓冲区分配的内存是通过sun.misc.Cleaner
实例释放的,一些特殊的幻像引用比对象终结更有效。
这种清理器机制是否仅在 JVM 中硬编码用于直接缓冲区子类,或者是否也可以在自定义组件中使用清理器(例如编写自定义直接字节缓冲区)?
这里我不是在谈论检索现有 nio 直接缓冲区的更干净的字段。我也不是在谈论手动释放内存。这是关于编写一个分配直接内存并通过垃圾收集器机制自动高效地清理它的新类。
在 Java 中,由 NIO 直接缓冲区分配的内存是通过sun.misc.Cleaner
实例释放的,一些特殊的幻像引用比对象终结更有效。
这种清理器机制是否仅在 JVM 中硬编码用于直接缓冲区子类,或者是否也可以在自定义组件中使用清理器(例如编写自定义直接字节缓冲区)?
这里我不是在谈论检索现有 nio 直接缓冲区的更干净的字段。我也不是在谈论手动释放内存。这是关于编写一个分配直接内存并通过垃圾收集器机制自动高效地清理它的新类。
在花更多时间阅读 API 文档(http://docs.oracle.com/javase/7/docs/api/java/lang/ref/package-summary.html)之后,我想我有一个更详细的答案:
1) 可以重用 sun.misc.Cleaner 来执行您自己的自定义类的有效清理。您可以通过调用提供的工厂方法来声明清洁器:
sun.misc.Cleaner.create(Object ob, Runnable cleanup);
有一段时间我无法让它正常工作,那是因为我愚蠢到将我的清洁器的可运行清理代码定义为一个匿名类,它保持对我的所指对象的(强)引用,防止它永远是“幻影触手可及”...
2)没有其他方法可以实现如此高效的清理(即使在幻像引用的帮助下)
实际上,引用处理程序线程以一种特殊的方式处理 sun.misc.Cleaner 的实例:
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
这意味着清理代码直接从引用处理程序线程调用,而在标准用法中,引用必须由引用处理程序线程入队,然后由另一个应用程序线程出列并处理。
如果你依赖sun.misc
包中的任何东西,你就会冒着它消失和破坏你的代码的风险。有些部分比其他部分更稳定,但这通常是一个坏主意(魔鬼的拥护者:其中的许多方法sun.misc.Unsafe
实际上是由 JVM 内在函数实现的,这使得它们比用户编写的 JNI 代码更快)。
在这种情况下,我认为这是一个坏主意:Cleaner
通过PhantomReference
. 还有其他的;谷歌示例。就此而言,您可以将其自身的源代码Cleaner
视为如何使用幻像引用的示例。
如果您要拥有引用堆外对象的堆上对象,您将需要某种清理处理程序。否则,当这些堆上对象被收集时,您将创建真正的内存泄漏。
如果使用java9,希望对您有所帮助。
下面的代码已经在 Intellij IDEA 2017 和 oracle jdk 9 中进行了测试。
import java.lang.ref.Cleaner;
public class Main {
public Main() {
}
public static void main(String[] args) {
System.out.println("Hello World!");
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Cleaner cleaner = Cleaner.create();
Main obj = new Main();
cleaner.register(obj, new Runnable() {
@Override
public void run() {
System.out.println("Hello World!222");
}
});
System.gc();
}
}
}