почему мой объект Java копируется или вызывается дважды?

Короче говоря, Java/Android/JNI... У меня есть два класса: объект/класс, который я создал под названием Packet, и интерфейс JNI для низкоуровневого кода C, который я написал. В одном классе я анализирую входящие пакеты и сохраняю их в 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();
    }
}

Отлично, это прекрасно работает, ДО тех пор, пока я не попытаюсь поделиться ArrayList со вторым классом. Для этого я использую Broadcast в Android, отправляя широковещательную рассылку со списком 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);

То, что я думал, было правдой по поводу этого, так это то, что каждый пакет в списке передается второму классу по ссылке на объект. Если бы это было правдой, то независимо от того, что два класса делают со списком и объектами в них, мне гарантировано, что сборщик мусора будет вызывать finalize() только ОДИН РАЗ для каждого пакета (когда оба класса считаются завершенными с каждым пакетом). Это очень важно, иначе функция free() будет вызываться дважды для одного и того же указателя (что приведет к SEGFAULT).

Кажется, это происходит со мной. Как только второй класс получает ArrayList из широковещательной рассылки, он анализирует его и содержит несколько пакетов из списка. При этом есть другой объект/класс, который имеет член типа Packet, если мне "нравится" пакет, я сохраняю его, выполнив:

other_object.pkt = pktFromList;

В конце концов, этот метод, получивший широковещательную рассылку, возвращается, а некоторые пакеты сохраняются. Наконец, когда я нажимаю кнопку (через несколько секунд), ArrayList в исходном классе очищается:

_scan_results.clear();

Я предположил, что даже когда здесь вызывается clear(), если первый класс «сохранил» некоторые пакеты, сборщик мусора не вызовет для них finalize(). Единственный способ, чтобы это было ложным, было бы, если: (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

Таким образом, одно из двух предположений, которые я делаю относительно передаваемых объектов, неверно. Кто-нибудь знает, что это может быть, и как я могу более тщательно передавать объекты по ссылке, чтобы я мог правильно очистить память?

Если вы зашли так далеко в моем вопросе и поняли его, спасибо;)


person gnychis    schedule 29.07.2011    source источник


Ответы (1)


Дополнительные намерения передаются по значению, а не по ссылке. Это означает, что копия того, что передается, создается получателем, когда он получает намерение.

Предположительно, вы пометили свой класс Packet как Parcelable, чтобы заставить его работать как дополнительное намерение. Parcelables маршалируются и не маршализируются — вы должны изменить свой код маршалинга для Packet, чтобы удалить указатель памяти, чтобы он не передавался копии, чтобы его нельзя было освободить дважды.

(Расширение на основе комментариев ниже и требования, чтобы он действовал «как если бы» он был передан по ссылке)

Создайте глобальный 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);
}
person antlersoft    schedule 29.07.2011
comment
Тогда это становится немного сложно. Я сделал свой класс Packet Serializable, чтобы он работал с дополнительным намерением. Это сложно, потому что этот указатель фактически используется для чего-то обоими классами. Если я не передам указатель второму классу, то он не сможет использовать для этого всю мою цель. Если первый класс освобождает указатель до того, как второй класс сможет его использовать, то это снова противоречит моей цели :) Важно, чтобы объект каким-то образом передавался по ссылке, иначе вся моя схема того, как все работает и будет освобождена, не будет Работа. - person gnychis; 29.07.2011
comment
@gnychis - Вы не можете передать это как дополнительное намерение; поместите его в свой объект приложения или некоторый глобальный HashMap и просто передайте ключ с вашим намерением. Вы можете использовать хэш-код объекта по умолчанию в качестве ключа. Затем получатель может удалить его, чтобы у вас не было оборванной ссылки, и он очистится, как вы ожидаете. - person antlersoft; 29.07.2011
comment
Хммм... если я сделаю это таким образом, что у меня есть какой-то глобальный HashMap или что-то в этом роде, это тоже может быть сложно. По сути, два класса находятся в двух разных потоках. Что я надеялся со всем материалом Broadcast и Intent, так это то, что оба потока могут работать и использовать пакеты, и мне не нужно беспокоиться о том, когда оба будут выполнены. Но теперь кажется, что мне нужно решить, когда удалить объект из Hashmap, чтобы память действительно в конечном итоге освободилась. В этот момент finalize() и GC мне не помогают. Или я что-то упускаю из предложенного вами подхода? - person gnychis; 29.07.2011
comment
Я задал этот вопрос, чтобы узнать, будут ли мои объекты копироваться по ссылке или объекту в моих потоках, чтобы это было проще. Вот ссылка на мой вопрос, который заставил меня подумать, что все это будет работать (с фоном в части потоков): stackoverflow.com/questions/6779019/ - person gnychis; 29.07.2011
comment
@gnychis - Если вы используете мой подход, и получатель удаляет ссылку в глобальной хеш-таблице, как только он получает намерение, это должно быть так же, как если бы оно было передано по ссылке. Если он отправлен из другого потока, вам придется обернуть доступ к pass_packets с помощью synchronize(passed_packets) { - person antlersoft; 29.07.2011
comment
аааааа, теперь я понял! По сути, я пытаюсь обойти широковещательное намерение, когда оно копируется объектом. Это имеет смысл для меня сейчас. Когда вы говорите, что если он отправлен из другого потока, вам придется обернуть доступ к pass_packets... - можете ли вы просто кратко рассказать об этом? Я не понимаю важность/значительность. Спасибо за вашу помощь, я искренне ценю это. - person gnychis; 29.07.2011
comment
Я обновил свой примерный код, чтобы показать, как вы должны защитить доступ к глобальному хешу, чтобы разные потоки видели его последовательно. - person antlersoft; 29.07.2011
comment
Большое спасибо, я ценю ваше время и понимание! Я дам этому шанс :) - person gnychis; 29.07.2011
comment
Последняя вещь. Одной из причин использования Broadcast было то, что неизвестное количество потоков могло получать данные и что-то с ними делать. Мне сейчас это не критично, потому что у меня всего два потока. Но есть ли у вас какие-либо идеи о том, как расширить это таким образом? Причина, по которой ваш метод работает только для двух потоков, заключается в том, что получатель трансляции удаляет ключ. После его удаления другой поток не сможет получить пакеты и т. д. - person gnychis; 29.07.2011
comment
В этот момент я начинаю подозревать, что вы используете намерения для чего-то, для чего они на самом деле плохо подходят :) Вместо того, чтобы явно удалять объект из HashMap, вы хотите использовать WeakHashMap, но это вводит дополнительный уровень сложность, и у вас возникнет проблема, если единственная ссылка на объект находится в системе распределения намерений. - person antlersoft; 29.07.2011
comment
хех, понятно. Знаете ли вы какой-нибудь другой примитив, который я должен/мог бы использовать вместо этого, который больше подходит для этого? Я в основном создаю список пакетов, затем я хочу передать их всем потокам, которые слушают, а затем GC для сбора (гарантируя, что finalize() вызывается один раз для каждого объекта Packet). У меня сейчас только две темы, но я думаю о ближайшем будущем и о будущем дизайне других связанных вещей. - person gnychis; 29.07.2011
comment
@gnychis позвольте нам продолжить это обсуждение в чате - person antlersoft; 29.07.2011