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

标签 java android debugging android-ndk finalize

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

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 我尝试与第二个类(class)共享 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 类型的成员,如果我“喜欢”该数据包,我会通过以下方式保存它:

other_object.pkt = pktFromList;

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

_scan_results.clear();

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

嗯,其中一个是错误的,因为 free() 偶尔会在同一 block 内存上调用两次。我看到它已分配(“新剖析: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

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

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

最佳答案

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

大概您已将您的 Packet 类标记为 Parcelable——以便让它作为 intent extra 工作。 Parcelables 被编码和解码——您应该更改 Packet 的编码代码以删除内存指针,这样它就不会传递给副本,因此它不会被释放两次。

(扩展,基于下面的评论和要求它表现得“好像”它已经通过引用传递)

创建全局 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())

收到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);
}

关于java - 为什么我的 Java 对象被复制或 finalize() 被调用两次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6875212/

相关文章:

java - 为什么插入排序在不同的实现中在时间上运行如此不同

java - 当 Log4j 创建 java.io.FileOutputStream 时,如何使用 Powermockito 模拟 java.io.FileOutputStream?

java - 如何在 Android 上检查我的应用程序堆内存大小?

java - 我无法弄清楚的基本java任务

perl - 是否可以在 Perl 调试器中设置断点而不修改源代码?

javascript - Firebug 在调试方面能更有用吗?备择方案?

java - 在 Graphics2D 元素上将文本居中时遇到问题

java - 当我将代码提取到依赖项时,我是否应该重新组织我的测试

Android:编译SDL选择的处理器不支持Thumb模式 `swp r3,r3,[r0]'

c++ - get_accChildCount 在不应该返回 0 的情况下返回 0