【问题标题】:why is my Java object being copied or finalize() being called twice?为什么我的 Java 对象被复制或 finalize() 被调用两次?
【发布时间】:2011-10-16 01:19:49
【问题描述】:

Java/Android/JNI 长话短说...我有两个类,一个是我创建的名为 Packet 的对象/类,另一个是我编写的低级 C 代码的 JNI 接口。在一个类中,我解析传入的数据包并将它们存储在一个 ArrayList 中。当它们被“解析”时,会调用一个函数 Packet.dissect(),它使用 JNI 调用较低级别的 C 代码。此 C 代码 malloc() 的一些内存,并将内存指针返回到 Java 代码,该 Java 代码将其存储在 Packet 对象的私有成员中:

ArrayList<Packet> _scan_results;

    ...

        // Loop and read headers and packets
        while(true) {
            Packet rpkt = new Packet(WTAP_ENCAP_IEEE_802_11_WLAN_RADIOTAP);

            switch(_state) {

            case IDLE:
                break;

            case SCANNING:
                // rpkt.dissect() calls a JNI C-code function which allocates
                // memory (malloc) and returns the pointer, which is stored in
                // a private member of the Packet (rpkt._dissect_ptr).
                rpkt.dissect();
                if(rpkt.getField("wlan_mgt.fixed.beacon")!=null)
                    _scan_results.add(rpkt);

                break;
            }
        }

现在,我想确保释放此内存,以免发生泄漏。因此,当垃圾收集器确定不再使用 Packet 对象时,我依靠 finalize() 调用 JNI C 代码函数,传递指针,从而释放内存:

protected void finalize() throws Throwable {
    try {

        if(_dissection_ptr!=-1)
            dissectCleanup(_dissection_ptr);

    } finally {
        super.finalize();
    }
}

太好了,效果很好直到我尝试与第二类共享 ArrayList。为此,我在 Android 中使用广播,方法是将带有 ArrayList 列表的广播从第一类发送到第二类中的 BroadcastReceiver。这是从头等舱广播的地方:

    // Now, send out a broadcast with the results
    Intent i = new Intent();
    i.setAction(WIFI_SCAN_RESULT);
    i.putExtra("packets", _scan_results);
    coexisyst.sendBroadcast(i);

认为这样做是对的,就是列表中的每个数据包都通过引用对象传递给第二个类。如果这是真的,那么无论这两个类对 List 和其中的对象做什么,我都保证垃圾收集器只会为每个 Packet 调用 finalize() ONCE (当两个类每个数据包都被视为已完成)。这非常重要,否则 free() 将在同一个指针上被调用两次(导致 SEGFAULT)。

这似乎发生在我身上。一旦第二个类从广播中接收到 ArrayList,它就会对其进行解析,并从列表中保存几个 Packet。这样做时,还有另一个对象/类有一个 Packet 类型的成员,如果我“喜欢”该数据包,我会通过以下方式保存它:

other_object.pkt = pktFromList;

最终这个接收广播的方法返回,并且一些数据包被“保留”。最后,当我单击一个按钮时(几秒钟后),原来类中的 ArrayList 被清除:

_scan_results.clear();

我假设即使在这里调用 clear(),如果第一个类“保留”了一些数据包,垃圾收集器也不会对它们调用 finalize()。唯一的方法是:(1)当广播被发送时,ArrayList 被复制而不是通过引用传递,或者(2)当数据包被“保留”在第二个类中,Packet 对象被复制而不是通过引用保存。

嗯,其中一个是错误的,因为 free() 偶尔会在同一块内存上调用两次。我看到它已分配(“新解剖:MEMORY_ADDRESS1 - IGNORE_THIS”),然后调用两个 finalize() 试图释放相同的内存地址:

INFO/Driver(6036): new dissection: 14825864 - 14825912
...
INFO/Driver(6036): received pointer to deallocate: 14825864
...
INFO/Driver(6036): received pointer to deallocate: 14825864
INFO/DEBUG(5946): signal 11 (SIGSEGV), fault addr 0000001c

因此,我对正在传递的对象所做的两个假设之一是错误的。有谁知道它可能是哪个,以及如何通过引用更仔细地传递对象,以便我可以正确清理内存?

如果你通过我的问题做到了这么远并理解它,谢谢;)

【问题讨论】:

    标签: java android debugging android-ndk finalize


    【解决方案1】:

    Intent extras 是按值传递的,而不是按引用传递的。这意味着接收者在接收到意图时会创建正在传递的任何内容的副本。

    大概你已经将你的 Packet 类标记为 Parcelable——为了让它作为一个额外的意图工作。 Parcelables 被编组和解组 - 您应该更改 Packet 的编组代码以删除内存指针,这样它就不会传递给副本,因此它不能被释放两次。

    (扩展,基于下面的 cmets 并要求它“好像”通过引用传递)

    创建一个全局HashMap&lt;int,ArrayList&lt;Packet&gt;&gt; passed_packets;

    在创建意图时,将现在作为额外对象传递给 HashMap:

    synchronized(passed_packets) {
        passed_packets.put(_scan_results.hashCode(), _scan_results);
    }
    

    将哈希键添加到意图以代替实际对象

    i.addExtra("packets_key", _scan_results.hashCode())
    

    接收到intent时,获取hash key,从HashMap中获取value,从HashMap中移除

    synchronized(passed_packets) {
        int key = i.getInt("packets_key");
        scan_results = passed_packets.get(key);
        passed_packets.remove(key);
    }
    

    【讨论】:

    • 这就有点棘手了。我使我的 Packet 类 Serializable 可以作为额外的意图使用。这很棘手,因为这两个类实际上都使用了该指针。如果我不将指针传递给第二类,那么它就不能使用我的全部目的。如果第一个类在第二个类使用它之前释放指针,那么它再次破坏了我的目的:) 对象以某种方式通过引用传递是很重要的,否则我关于事物如何工作和将被释放的整个方案将不会工作。
    • @gnychis -- 你不能将这个东西作为一个额外的意图传递;将它放在您的应用程序对象或一些全局 HashMap 中,然后按照您的意图传递密钥。您可以使用默认对象哈希码作为键。然后接收器可以删除它,这样你就没有悬空的引用,它会按照你的预期清理。
    • 嗯...如果我这样做,因为有一些全局 HashMap 或类似的东西,它也可能很难。本质上,这两个类在两个不同的线程中。我对整个 Broadcast 和 Intent 的期望是,两个线程都可以运行并使用数据包,而我不必担心何时完成。但现在似乎我需要决定何时从 Hashmap 中删除对象,以便最终释放内存。在这一点上, finalize() 和 GC 对我没有帮助。还是我错过了您建议的方法?
    • 我问过这个问题,看看我的对象是否会通过引用或对象跨线程复制,这样会更简单。这是一个指向我的问题的链接,它让我认为这一切都会奏效(线程部分有背景):stackoverflow.com/questions/6779019/…
    • @gnychis -- 如果你使用我的方法并且接收者在收到意图后立即删除全局哈希表中的引用,它应该与通过引用传递一样。如果它是从不同的线程发送的,则必须使用 synchronize(passed_pa​​ckets) { 包装对passed_pa​​ckets 的访问
    猜你喜欢
    • 2014-09-17
    • 1970-01-01
    • 1970-01-01
    • 2021-07-13
    • 2021-11-25
    • 2015-12-24
    • 2012-12-10
    • 2014-01-11
    相关资源
    最近更新 更多