验证Java中的对象删除

时间:2017-03-17 12:24:33

标签: java unit-testing testing garbage-collection

在我的代码中,在运行时创建和删除了许多对象,这些对象可以有很长的引用链(即可以从许多地方进行垃圾收集)。

在我的单元测试中,我想验证当我“删除”某个对象时,例如从主要包含它的列表中删除它,删除对该对象的所有引用。要明确:我想验证在我的应用程序中不再存在对此对象的引用。

我怎样才能做到这一点?

到目前为止,我只对此进行了测试(测试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说:

  

public static void gc()

     

运行垃圾收集器。   调用gc方法表明Java虚拟机花费了大量精力来回收未使用的对象,以使其当前占用的内存可用于快速重用。 当控制从方法调用返回时,Java虚拟机已尽最大努力从所有丢弃的对象中回收空间。

(强调我的)

这是非常含糊的(“尽力而为”)并且似乎非常不确定,例如我必须等待1秒钟再次拨打gc(),或者拨打电话,例如连续四次将参考队列入队列。

有没有办法正确地做到这一点?我想测试对象不会持久存在,并且最终会在几个正常运行时间之后充斥我的记忆。

说明:

1。)我知道你不应该致电gc(),因为这是错误的代码。没错,但是我从测试代码中调用它来验证生产代码。

2。)我知道gc()从本地的角度来看实际上不是确定性的。我知道这一点并要求更好的选择。

修改

我意识到这个可以是一种“特殊的”单元测试:如果它对于给定的代码库成功一次,我知道这个引用是垃圾收集的;如果单元测试失败,可能是因为gc()不想删除它,或者它无法删除。因此,此测试可以验证内存是否已释放,但可能需要多次尝试才能进行验证。但是,这是“特殊的”(读作:“坏”),因为我必须接受随机失败的单元测试。

3 个答案:

答案 0 :(得分:0)

AFAIK无法确保对象已被垃圾回收。调用System.gc()通常会导致垃圾回收,但并非总是如此。

我还有一个无限运行的Java进程 - 我在每次迭代开始时调用System.gc()并记录所产生的已用内存量。如果这个数量随着时间的推移没有显着变化,我可以合理地确保不会“泄漏”记忆。

答案 1 :(得分:0)

我观察到当触发堆转储时,它通常会执行完整的GC。如果这是JVM在Heap集合期间强制GC的策略,那么您可以通过

触发堆集合
kill -3 pid

看看它是否有效。

  

您可能需要添加jvm参数以获取堆转储。

答案 2 :(得分:0)

我没有看到使用引用和System.gc()进行测试的问题(虽然我更喜欢弱引用而不是幻影引用)。我不记得这种方法有任何稳定性问题。

仅当您可以参考可能泄露的对象时,基于参考的方法才有效。

我有来自NetBeans的头部转储分析库的forked version,我用它来自动化某些类型的内存使用报告。

对于测试代码,可以对其自身进行堆转储,并检查此转储断言给定类型的特定对象数量。

Here你可以找到这种方法的例子。