【发布时间】:2017-03-17 12:24:32
【问题描述】:
在我的代码中,在运行时创建和删除了许多对象,这些对象可能有很长的引用链(即可以从许多地方进行垃圾收集)。
在我的单元测试中,我想验证当我“删除”一个对象时,例如从主要包含它的列表中删除它,对该对象的所有引用都将被删除。明确一点:我想验证我的应用程序中不再存在对此对象的引用。
我怎样才能做到这一点?
到目前为止,我只想到了这个(测试 PhantomReference 是否已入队):
@Test
public void test_objectX_can_be_garbage_collected() throws Exception {
ReferenceQueue<MyClass> refsToBeDeleted = new ReferenceQueue<>();
MyClass obj = new MyClass();
PhantomReference<MyClass> phantomRef = new PhantomReference<MyClass>(obj, refsToBeDeleted);
obj = null; // delete last strong reference to obj
System.out.println("Reference is enqueued:" + phantomRef.isEnqueued()); // "false"
System.gc(); // invoke garbage collection
Thread.sleep(1000L); // required - see text
System.gc(); // invoke garbage collection again
System.out.println("Reference is enqueued:" + phantomRef.isEnqueued()); // true
}
与
public static class MyClass {
}
doc 说:
公共静态 void gc()
运行垃圾收集器。 调用 gc 方法表明 Java 虚拟机花费精力回收未使用的对象,以使它们当前占用的内存可用于快速重用。 当控制从方法调用返回时,Java 虚拟机已尽最大努力从所有丢弃的对象中回收空间。
(强调我的)
这是相当模糊的(“尽力而为”),并且似乎非常不确定,例如我必须等待 1 秒并再次致电 gc(),或者将其称为例如连续四次将引用排入队列。
有没有办法正确地做到这一点?我想测试对象不会持续存在,并且在几个月的正常运行后最终会淹没我的记忆。
备注:
1.) 我知道你不应该打电话给gc(),因为它是糟糕的代码。没错,但我是从测试代码调用它来验证生产代码。
2.) 我知道gc() 从本地的角度来看实际上不是确定性的。我知道这一点并寻求更好的选择。
编辑:
我意识到这个可以是一种“特殊”的单元测试:如果它对给定的代码库成功一次,我知道这个引用是垃圾收集的;如果单元测试失败,可能是因为gc() 不想删除它,或者它无法删除。因此,此测试可以验证内存是否已释放,但可能需要多次尝试才能进行验证。然而,这是“特殊的”(读作:“坏的”),因为我必须接受随机失败的单元测试。
【问题讨论】:
-
使用 Java 内存分析器会更容易吗?
-
@SteveSmith 可能是这种情况 - 我不得不承认我从未使用过...但是我想自动测试它。
-
一个随机的危险和hacky想法:启动一个具有相对较小堆的新VM。在此 VM 中创建对象,然后删除所需的对象,然后创建一个无限链表来消耗堆,直到抛出 OutOfMemoryError,从而确保执行 gc。检查引用现在是否已清除。如果清除,则测试通过。如果不是,则测试失败。这将是确定性的,但可能非常难以创建和维护。我不确定 VM 在 OutOfMemoryError 之后的行为如何,因此这个想法可能根本行不通。是否为我的愚蠢建议付出努力和时间由您决定。
-
恕我直言,您可以在测试中依赖
System.gc。它不是随机的,它始终有效,除非关闭。只要没有人这样做,它就不会关闭。所以不要这样做并保护你的计算。 :D -
@maaartinus 谢谢,但我不明白为什么我必须调用 System.gc() 几次 (或者在睡眠之间,或者三到四次,在我的机器上)成功地将该引用排入队列。我怎么知道“正确”的频率是多少?这不是在文档中指定的方式(“当 gc 返回时,它已经尽最大努力......”),因为如果在连续三个调用后尽力而为成功,那么第一次调用不是尽力而为恕我直言。
标签: java unit-testing testing garbage-collection