2

Java/Android/JNI の長い話を簡単に言うと、Packet という名前で作成したオブジェクト/クラスと、私が作成した下位レベルの C コードへの JNI インターフェイスの 2 つのクラスがあります。あるクラスでは、着信パケットを解析して ArrayList に格納します。それらが「解析」されると、関数 Packet.dissect() が呼び出され、JNI を使用して下位レベルの C コードを呼び出します。この C コード malloc() の一部のメモリは、Packet オブジェクトのプライベート メンバーに格納する 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();
    }
}

2 番目のクラスとArrayListを共有しようとするまで、これは問題なく機能します。そのために、最初のクラスの ArrayList リストを含むブロードキャストを 2 番目のクラスの BroadcastReceiver に送信することで、Android で Broadcast を使用します。最初のクラスから放送される場所は次のとおりです。

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

これを行うことについて私が正しいと思ったのは、リスト内の各パケットがオブジェクトへの参照によって 2 番目のクラスに渡されるということです。これが真である場合、2 つのクラスが List とその中のオブジェクトをどのように処理しても、ガベージ コレクターは各パケットに対して 1 回だけ finalize() を呼び出すことが保証されます(両方のクラスが各パケットで終了したと見なされる場合)。 . これは非常に重要です。そうしないと、同じポインターに対して free() が 2 回呼び出されます (SEGFAULT が発生します)。

これは私に起こっているようです。2 番目のクラスがブロードキャストから ArrayList を受け取ると、それを解析し、リストからいくつかのパケットを保持します。そうするとき、パケット型のメンバーを持つ別のオブジェクト/クラスがあり、そのパケットが「気に入った」場合は、次のようにして保存します。

other_object.pkt = pktFromList;

最終的に、ブロードキャストを受信したこのメソッドが戻り、一部のパケットが「保持」されました。最後に、ボタンをクリックすると (数秒後)、元のクラスの ArrayList がクリアされます。

_scan_results.clear();

ここで clear() が呼び出された場合でも、最初のクラスがパケットの一部を「保持」している場合、ガベージ コレクターはそれらに対して finalize() を呼び出さないと想定しています。これが false になる唯一の方法は、(1) ブロードキャストが送信されるときに ArrayList がコピーされ、参照によって渡されない場合、または (2) パケットが 2 番目のクラスである Packet オブジェクトに「保持」される場合です。参照によって保持されるのではなく、コピーされます。

free() が同じメモリの断片に対して 2 回呼び出されることがあるからです。私はそれが割り当てられていることを確認し(「新しい解剖:MEMORY_ADDRESS1 - IGNORE_THIS」)、同じメモリアドレスの割り当てを解除しようとして2つの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

したがって、渡されるオブジェクトについて私が行っている 2 つの仮定のうちの 1 つは誤りです。メモリを適切にクリーンアップできるように、参照によってオブジェクトをより慎重に渡す方法を知っている人はいますか?

私の質問をここまで読んで理解していただけたなら、ありがとうございます ;)

4

1 に答える 1

2

インテント エクストラは、参照ではなく、値によって渡されます。これは、受信者がインテントを受信したときに、渡されたもののコピーが受信者によって作成されることを意味します。

おそらく、Packet クラスを Parcelable としてマークして、インテント エクストラとして機能させるためです。Parcelable はマーシャリングおよびアンマーシャリングされます。Packet のマーシャリング コードを変更してメモリ ポインタをドロップし、メモリ ポインタがコピーに渡されないようにして、2 回解放されないようにする必要があります。

(以下のコメントと、参照によって渡されたかのように動作するという要件に基づいて拡張)

グローバルを作成するHashMap<int,ArrayList<Packet>> passed_packets;

インテントを作成するときに、HashMap にエクストラとして渡されるオブジェクトを追加します。

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

実際のオブジェクトの代わりにインテントにハッシュ キーを追加します

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);
}
于 2011-07-29T15:24:01.050 に答える