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
因此,我对传递的对象做出的两个假设之一是假的。有谁知道它可能是什么,以及如何通过引用更仔细地传递对象,以便我可以正确清理内存?
如果你通过我的问题做到这一点并理解它,谢谢你;)
答案 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);
}