开发者

why is my Java object being copied or finalize() being called twice?

开发者 https://www.devze.com 2023-03-24 00:52 出处:网络
Long Java/Android/JNI story short... I have two classes, an object/class which I created called Packet, and a JNI interface to lower-level C code that I wrote.In one class, I parse incoming packets an

Long Java/Android/JNI story short... I have two classes, an object/class which I created called Packet, and a JNI interface to lower-level C code that I wrote. In one class, I parse incoming packets and store them in an ArrayList. When they are "parsed" a function Packet.dissect() is called, which uses JNI to call lower-level C-code. This C-code malloc()'s some memory, and returns the memory pointer to the Java code which stores it in a private member of the Packet object:

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;
            }
        }

Now, I want to ensure that this memory is freed so that I do not have a leak. So, when the garbage collector determines that the Packet object is no longer in use, I rely on finalize() to call a JNI C-code function, passing the pointer, which frees the memory:

protected void finalize() throws Throwable {
    try {

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

    } finally {
        super.finalize();
    }
}

Great, this works just fine UNTIL I attempt to share the ArrayList with the second class. To do so, I use Broadcast in Android by sending a broadcast with the ArrayList list from the first class, to a BroadcastReceiver in the second class. Here is where it is broadcast from the first class:

    // 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);

What I thought was true about doing this, would be that each Packet in the list is passed to the second class by reference to the object. If this were true, then no matter what the two classes do with the List and the objects in them, I am guaranteed that the garbage collector will only call finalize() ONCE for each Packet (when both classes are deemed finished with each packet). This is super important, or free() will be called twice on the same pointer (causing a SEGFAULT).

This seems to be happening to me. Once the second class receives the ArrayList from the broadcast, it parses through it, and holds several Packet's from the list. When doing so, there is another object/class which has a member of type Packet, if I "like" the packet, I save it by doing:

other_object.pkt = pktFromList;

Eventually this method which received the Broadcast returns, and some of the Packets were "kept." Finally, when I click a button (several seconds later), the ArrayList in the original class is cleared:

_scan_results.clear();

I have assumed that even when clear() is called here, if the first class has "kept" some of the packets, the garbage collector will not call finalize() on them. The only way for this to be false would be if: (1) when the broadcast is sent, the ArrayList is copied and not passed by reference, or (2) when the packets are "kept" in the second class, t开发者_如何学JAVAhe Packet object is copied rather than being kept by reference.

Well, one of these is false because free() is occasionally called on the same piece of memory twice. I see that it is allocated ("new dissection: MEMORY_ADDRESS1 - IGNORE_THIS"), and then two finalize() are called trying to deallocate the same memory address:

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

So, one of the two assumptions I am making about the objects being passed around is false. Does anyone know which it might be, and how I can pass the objects more carefully by reference so that I can clean the memory up properly?

If you made it this far through my question and understand it, thank you ;)


Intent extras are passed by value, not by reference. This means a copy of whatever is being passed is created by the receiver when it receives the intent.

Presumably you have marked your Packet class as a Parcelable-- in order to get it to work as an intent extra. Parcelables are marshaled and unmarshaled -- you should change your marshaling code for Packet to drop the memory pointer so it won't be passed on to the copy so it can't be released twice.

(Expanding, based on comments below and requirement that it act "as if" it had been passed by reference)

Create a global HashMap<int,ArrayList<Packet>> passed_packets;

When creating the intent, add the object now being passed as an extra to the HashMap:

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

Add the hash key to the intent in lieu of the actual object

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

When receiving the intent, get the hash key, get the value from the HashMap, and remove it from the HashMap

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

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号