为什么我的Java对象被复制或者finalize()被调用两次?

时间:2011-07-29 15:10:31

标签: java android debugging android-ndk finalize

Long Java / Android / JNI故事简短...我有两个类,我创建的一个对象/类叫做Packet,还有我编写的低级C代码的JNI接口。在一个类中,我解析传入的数据包并将它们存储在ArrayList中。当它们被“解析”时,调用函数Packet.dissect(),它使用JNI来调用较低级别的C代码。这个C代码malloc()有一些内存,并返回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();
    }
}

很好,这很好用 UNTIL 我尝试与第二个类共享ArrayList。为此,我在Android中使用Broadcast,通过从第一个类发送带有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及其中的对象做什么,我保证垃圾收集器只会为每个数据包调用finalize() ONCE (当这两个类都有时)被视为已完成每个数据包)。这非常重要,或者free()将在同一个指针上调用两次(导致SEGFAULT)。

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

other_object.pkt = pktFromList;

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

_scan_results.clear();

我假设即使在这里调用clear(),如果第一个类“保留”了一些数据包,垃圾收集器也不会调用它们的finalize()。唯一的方法是假的:if:(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

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

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

1 个答案:

答案 0 :(得分:2)

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

据推测,您已将Packet类标记为Parcelable--以使其作为额外的意图工作。 parcelables被封送和解组 - 你应该改变你的封送编组代码以丢弃内存指针,这样它就不会被传递给副本,所以它不能被释放两次。

(根据以下评论进行扩展,并要求其“好像”通过引用传递)

创建全局HashMap<int,ArrayList<Packet>> passed_packets;

创建intent时,将现在作为额外传递的对象添加到HashMap:

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

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

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

收到意图时,获取哈希键,从HashMap中获取值,然后将其从HashMap中删除

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