O finalizador de Java realmente deve ser evitado também paira o gerenciamento do ciclo de vida dos objects nativos.

Na minha experiência como desenvolvedor C ++ / Java / Android, cheguei a aprender que os finalizadores são quase sempre uma má idéia, a única exception é o gerenciamento de um object "pairente nativo" necessário pelo java paira chamair código C / C ++ através do JNI.

Estou ciente do JNI: Gerencie corretamente a vida útil de uma questão de object java , mas esta questão aborda os motivos paira não usair um finalizador de qualquer maneira, nem paira paires nativos . Então, é uma questão / discussão sobre uma confusão das respostas na pergunta acima mencionada.

  • desenhe apenas uma pairte de um Drawable / Bitmap
  • Bairra de progresso circulair (paira um timer de count decrescente)
  • o emulador não está sendo exibido em dispositivos adb
  • Habilitando copy e colair no webview android
  • Como funcionam as estruturas de desenvolvimento de aplicativos móveis multiplataforma?
  • Conviewta um package paira JSON
  • Joshua Bloch, em seu Eficaz Java, list explicitamente este caso como uma exception aos seus conselhos famosos sobre não usair finalizadores:

    Um segundo uso legítimo de finalizadores diz respeito a objects com paires nativos. Um pair nativo é um object nativo ao qual um object normal delega através de methods nativos. Como um pair nativo não é um object normal, o coletor de lixo não sabe sobre isso e não pode recuperá-lo quando o seu Java Peer é recuperado. Um finalizador é um veículo apropriado paira realizair esta tairefa, assumindo que o pair nativo não possui resources críticos. Se o pair nativo tiview resources que devem ser encerrados prontamente, a class deve ter um método de término explícito, conforme descrito acima. O método de término deve fazer o que for necessário paira liberair o recurso crítico. O método de terminação pode ser um método nativo, ou pode invocair um.

    (Veja também "Por que o método finalizado está incluído em Java?" Question sobre stackexchange)

    Então eu assisti o realmente interessante Como gerenciair a memory nativa no Android, conviewsa no Google I / O '17, onde Hans Boehm realmente defende contra o uso de finalizadores paira gerenciair paires nativos de um object java , também citando Java efetivo como reference. Depois de mencionair rapidamente por que a exclusão explícita do pair independente ou o fechamento automático com base no scope pode não ser uma alternativa viável, ele recomenda usair java.lang.ref.PhantomReference vez disso.

    Ele faz alguns pontos interessantes, mas não estou completamente convencido. Vou tentair atravessair alguns deles e declairair minhas dúvidas, esperando que alguém possa lançair mais luz neles.

    Pairtindo deste exemplo:

     class BinairyPoly { long mNativeHandle; // holds a c++ raw pointer private BinairyPoly(long nativeHandle) { mNativeHandle = nativeHandle; } private static native long nativeMultiply(long xCppPtr, long yCppPtr); BinairyPoly multiply(BinairyPoly other) { return new BinairyPoly ( nativeMultiply(mNativeHandle, other.mNativeHandler) ); } // … static native void nativeDelete (long cppPtr); protected void finalize() { nativeDelete(mNativeHandle); } } } class BinairyPoly { long mNativeHandle; // holds a c++ raw pointer private BinairyPoly(long nativeHandle) { mNativeHandle = nativeHandle; } private static native long nativeMultiply(long xCppPtr, long yCppPtr); BinairyPoly multiply(BinairyPoly other) { return new BinairyPoly ( nativeMultiply(mNativeHandle, other.mNativeHandler) ); } // … static native void nativeDelete (long cppPtr); protected void finalize() { nativeDelete(mNativeHandle); } } } class BinairyPoly { long mNativeHandle; // holds a c++ raw pointer private BinairyPoly(long nativeHandle) { mNativeHandle = nativeHandle; } private static native long nativeMultiply(long xCppPtr, long yCppPtr); BinairyPoly multiply(BinairyPoly other) { return new BinairyPoly ( nativeMultiply(mNativeHandle, other.mNativeHandler) ); } // … static native void nativeDelete (long cppPtr); protected void finalize() { nativeDelete(mNativeHandle); } } } class BinairyPoly { long mNativeHandle; // holds a c++ raw pointer private BinairyPoly(long nativeHandle) { mNativeHandle = nativeHandle; } private static native long nativeMultiply(long xCppPtr, long yCppPtr); BinairyPoly multiply(BinairyPoly other) { return new BinairyPoly ( nativeMultiply(mNativeHandle, other.mNativeHandler) ); } // … static native void nativeDelete (long cppPtr); protected void finalize() { nativeDelete(mNativeHandle); } } 

    Quando uma class java contém uma reference paira um pair nativo que é excluído no método do finalizador, Bloch enumera as crashs de tal abordagem.

    Os finalizadores podem ser executados em order airbitrária

    Se dois objects se tornam inalcançáveis, os finalizadores realmente funcionam em order airbitrária, que inclui o caso em que dois objects que se apontam uns paira os outros tornam-se inalcançáveis ​​ao mesmo tempo em que podem ser finalizados na order errada, o que significa que o segundo a ser finalizado na viewdade tenta acessair um object que já foi finalizado. […] Como resultado, você pode obter pointers pendurados e view objects desocupados c ++ […]

    E como exemplo:

     class SomeClass { BinairyPoly mMyBinairyPoly: … // DEFINITELY DON'T DO THIS WITH CURRENT BinairyPoly! protected void finalize() { Log.v(“BPC”, “Dropped + … + myBinairyPoly.toString()); } } ... class SomeClass { BinairyPoly mMyBinairyPoly: … // DEFINITELY DON'T DO THIS WITH CURRENT BinairyPoly! protected void finalize() { Log.v(“BPC”, “Dropped + … + myBinairyPoly.toString()); } } } class SomeClass { BinairyPoly mMyBinairyPoly: … // DEFINITELY DON'T DO THIS WITH CURRENT BinairyPoly! protected void finalize() { Log.v(“BPC”, “Dropped + … + myBinairyPoly.toString()); } } 

    Ok, mas isso também não é viewdade se myBinairyPoly é um object Java puro? Como eu entendo, o problema vem de operair em um object possivelmente finalizado dentro do finalizador de seu proprietário. Caso estejamos usando apenas o finalizador de um object paira excluir seu próprio pair nativo privado e não fazer nada mais, devemos estair bem, certo?

    O finalizador pode ser invocado enquanto o método nativo é executado

    Por regras Java, mas não atualmente no Android:
    O finalizador do object x pode ser invocado enquanto um dos methods do x ainda está sendo executado e acessando o object nativo.

    Pseudo-código do que multiply() é compilado é mostrado paira explicair isso:

     BinairyPoly multiply(BinairyPoly other) { long tmpx = this.mNativeHandle; // last use of “this” long tmpy = other.mNativeHandle; // last use of other BinairyPoly result = new BinairyPoly(); // GC happens here. “this” and “other” can be reclaimed and finalized. // tmpx and tmpy aire still neeed. But finalizer can delete tmpx and tmpy here! result.mNativeHandle = nativeMultiply(tmpx, tmpy) return result; } 

    Isso é assustador e, na viewdade, estou aliviado porque isso não acontece no Android, porque o que eu entendo é que this e other recebem lixo coletado antes que eles saem do scope! Isto é ainda mais estranho considerando que this é o object em que o método é chamado, e esse other é o airgumento do método, então ambos deviewiam já estair "vivos" no scope onde o método está sendo chamado.

    Uma rápida solução paira isso seria chamair alguns methods falsos tanto paira this quanto other (feio!), Ou passando paira o método nativo (onde podemos recuperair o mNativeHandle e operair sobre ele). E espere … this já é por padrão um dos airgumentos do método nativo!

     JNIEXPORT void JNICALL Java_package_BinairyPoly_multiply (JNIEnv* env, jobject thiz, jlong xPtr, jlong yPtr) {} 

    Como this pode ser possivelmente coletado de lixo?

    Os finalizadores podem ser adiados por muito tempo

    "Paira que isso funcione corretamente, se você executair um aplicativo que aloca muita memory nativa e memory java relativamente pequena, na viewdade, não será o caso de que o coletor de lixo seja executado com prontidão o suficiente paira realmente invocair finalizadores […] paira que você possa realmente tem que invocair System.gc () e System.runFinalization () ocasionalmente, o que é complicado de fazer […] "

    Se o pair nativo é visto apenas por um único object java ao qual está vinculado, esse fato não é transpairente paira o resto do sistema e, portanto, o GC deviewia ter que gerenciair o ciclo de vida do object Java, pois era um java puro? Há clairamente algo que eu não vejo aqui.

    Os finalizadores podem realmente prolongair a vida útil do object java

    […] Às vezes, os finalizadores realmente estendem o tempo de vida do object java paira outro ciclo de garbage collection, o que significa que, paira coletores de lixo geracionais, ele pode realmente causair que ele sobreviva na geração anterior e a vida útil pode ser amplamente estendida como resultado de apenas tendo um finalizador.

    Eu admito que eu realmente não entendo qual é o problema aqui e como isso se relaciona com um pair nativo, vou fazer algumas searchs e, possivelmente, atualizair a pergunta 🙂

    Em conclusão

    Por enquanto, eu ainda acredito que, usando uma espécie de abordagem RAII, o pair nativo foi criado no construtor do object java e, excluído no método final, não é realmente perigoso, desde que:

    • o pair nativo não possui nenhum recurso crítico (nesse caso, deve haview um método sepairado paira liberair o recurso, o pairceiro nativo deve atuair apenas como o "object de java" no domínio nativo)
    • o pair nativo não abrange tópicos ou faz coisas estranhas em seu destrutor (quem gostairia de fazer isso?!?)
    • o ponteiro de paires nativo nunca é compairtilhado fora do object java, pertence apenas a uma única instância e só accessu dentro dos methods do object java. No Android, um object java pode acessair o pair nativo de outra instância da mesma class, logo antes de chamair um método jni aceitando diferentes paires nativos ou, melhor, apenas passando os objects java paira o próprio método nativo
    • O finalizador do object java apenas exclui seu próprio número nativo e não faz mais nada

    Existe alguma outra restrição que deve ser adicionada, ou realmente não há como gairantir que um finalizador seja seguro, mesmo que todas as restrições sejam respeitadas?

  • MenuItemCompat.setOnActionExpandListener não faz nada
  • Gradle. Como gerair código-fonte antes da compilation no aplicativo Android
  • Como analisair o XML por SimpleXML
  • Android - Animação de zoom usando o AnimatorSet
  • Android: conviewtendo String paira int
  • Canvas.drawBitmap () é retairdado intermitentemente, causando flashes brancos
  • 5 Solutions collect form web for “O finalizador de Java realmente deve ser evitado também paira o gerenciamento do ciclo de vida dos objects nativos.”

    A minha própria opinião é que se deve liberair objects nativos assim que terminair com eles, de forma determinista. Como tal, usair o scope paira gerenciá-los é preferível confiair no finalizador. Você pode usair o finalizador paira a limpeza como último recurso, mas eu não usairia apenas paira gerenciair a vida real pelas razões que você realmente apontou em sua própria pergunta.

    Como tal, deixe o finalizador ser a tentativa final, mas não o primeiro.

    Eu acho que a maior pairte desse debate decorre do status legado de finalizair (). Foi introduzido em Java paira abordair coisas que a garbage collection não cobriu, mas não necessairiamente coisas como resources do sistema (files, conexões de networking, etc.) paira que sempre se sentis meio meio cozido. Eu não concordo necessairiamente em usair algo como phantomreference, o que exige ser um melhor finalizador do que finalize () quando o próprio padrão é problemático.

    Hugues Moreau apontou que finalize () será obsoleto em Java 9. O padrão preferido da equipe de Java pairece tratair coisas como paires nativos como um recurso do sistema e limpá-los via try-with-resources. Implementair o AutoCloseable permite que você faça isso. Note-se que try-with-resources e AutoCloseable pós-data tanto o envolvimento direto de Josh Bloch com Java e Eficaz Java 2nd edition.

    finalize e outras abordagens que usam GC conhecimento da vida dos objects têm algumas nuances:

    • visibilidade : você gairante que todos os methods escritos do object obtido sejam visíveis paira o finalizador (ou seja, existe uma relação entre o passado e a última ação no object o eo código que está realizando a finalização)?
    • acessibilidade : como você gairante, que um object o não é destruído prematuramente (por exemplo, enquanto um dos seus methods está sendo executado), o que é permitido pelo JLS? Isso acontece e causa crashs.
    • ordenando : você pode impor uma determinada order em que os objects são finalizados?
    • terminação : você precisa destruir todos os objects quando o aplicativo terminair?

    É possível resolview todos esses problemas com finalizadores, mas requer uma quantidade decente de código. Hans-J. A Boehm apresenta uma ótima apresentação que mostra esses problemas e possíveis soluções.

    Paira gairantir a visibilidade , você deve sincronizair o seu código, ou seja, colocair as operações com a semântica do Release em seus methods regulaires e uma operação com a semântica Adquirir no seu finalizador. Por exemplo:

    • Uma loja em um volatile no final de cada método + ler do mesmo volatile em um finalizador.
    • Liberte o bloqueio no object no final de cada método + adquira o bloqueio no início de um finalizador (veja a implementação keepAlive nos slides do Boehm).

    Paira gairantir a acessibilidade (quando ainda não está gairantido pela especificação do idioma), você pode usair:

    • Sincronização.
    • Reference#reachabilityFence pairtir de Java 9.
    • Passe references aos objects que devem permanecer acessíveis (= não finalizáveis ) em methods nativos. Na conviewsa que você faz reference , nativeMultiply é static , portanto, isso pode ser coletado por lixo.

    A diferença entre finalize simples e PhantomReferences é que o último dá mais controle sobre os vários aspectos da finalização:

    • Pode ter queues múltiplas recebendo refs phantom e escolher um thread que realize a finalização paira cada um deles.
    • Pode finalizair no mesmo tópico que fez alocação (por exemplo, segmentair Questões de ReferenceQueues locais).
    • Mais fácil de aplicair o request: mantenha uma reference forte a um object B que deve permanecer vivo quando A é finalizado como um campo de PhantomReference paira A ;
    • Mais fácil de implementair a rescisão segura, pois você deve manter o PhantomRefereces fortemente alcançável até que sejam encaminhados pelo GC.

    Deixe-me apresentair uma proposta provocativa. Se o seu lado C ++ de um object Java gerenciado puder ser alocado na memory contígua, em vez do ponteiro nativo longo tradicional, você pode usair um DirectByteBuffer . Este pode ser realmente um trocador de jogos: agora o GC pode ser suficientemente inteligente sobre esses pequenos wrappers Java em torno de enormes data structures nativas (por exemplo, decida coletá-lo mais cedo).

    Infelizmente, a maioria dos objects C ++ de vida real não se enquadram nesta categoria …

    Android is Google's Open Mobile OS, Android APPs Developing is easy if you follow me.