Kotlin: safe lambdas (no hay fugas de memoria)?

Después de haber leído este artículo sobre fugas de memoria , me pregunto si el uso de lambdas en el proyecto Kotlin Android es seguro. Es cierto que la sintaxis lambda me hace programar con más facilidad, pero ¿qué pasa con las fugas de memoria?

Como ejemplo de la problemática, he tomado un pedazo de código de uno de mis proyectos, donde construyo un AlertDialog. Este código está dentro de la clase MainActivity de mi proyecto.

fun deleteItemOnConfirmation(id: Long) : Unit { val item = explorerAdapter.getItemAt(id.toInt()) val stringId = if (item.isDirectory) R.string.about_to_delete_folder else R.string.about_to_delete_file val dialog = AlertDialog.Builder(this). setMessage(String.format(getString(stringId), item.name)).setPositiveButton( R.string.ok, {dialog: DialogInterface, id: Int -> val success = if (item.isDirectory) ExplorerFileManager.deleteFolderRecursively(item.name) else ExplorerFileManager.deleteFile(item.name) if (success) { explorerAdapter.deleteItem(item) explorerRecyclerView.invalidate() } else Toast.makeText(this@MainActivity, R.string.file_deletion_error, Toast.LENGTH_SHORT).show() }).setNegativeButton( R.string.cancel, {dialog: DialogInterface, id: Int -> dialog.cancel() }) dialog.show() } 

Mi pregunta es muy simple: ¿pueden los dos lambdas establecidos para los botones positivos y negativos llevar a fugas de memoria? (También quiero decir, son kotlin lambdas simplemente convertido en Java Anónimo funciones?)

Edit: Tal vez tengo mi respuesta en este tema de Jetbrains .

Edit (19 de febrero de 2017): Recibí una respuesta muy completa de Mike Hearn sobre este tema:

Al igual que en Java, lo que sucede en Kotlin varía en diferentes casos.

  • Si el lambda se pasa a una función en línea y no está marcado noinline, entonces todo desaparece y no se crean clases u objetos adicionales.
  • Si el lambda no captura, entonces se emitirá como una clase singleton cuya instancia se reutiliza una y otra vez (una clase + una asignación de objetos).
  • Si el lambda captura, entonces se crea un nuevo objeto cada vez que se usa el lambda.

Por lo tanto, es un comportamiento similar a Java, excepto por el caso inline en el que es incluso más barato. Este enfoque eficiente para codificar lambdas es una razón por la que la programación funcional en Kotlin es más atractiva que en Java.


Editar (17 de febrero de 2017): He publicado una pregunta sobre este tema en las discusiones de Kotlin . Tal vez los ingenieros de Kotlin traigan algo nuevo a la mesa.


Son kotlin lambdas simplemente convertido en Java Anónimo funciones?

Estaba haciendo esta pregunta yo mismo (una simple corrección aquí: se llaman clases anónimas , no funciones). No hay una respuesta clara en la documentación de Koltin . Simplemente afirman que

El uso de funciones de orden superior impone ciertas penalidades de tiempo de ejecución: cada función es un objeto y captura un cierre, es decir, aquellas variables a las que se accede en el cuerpo de la función.

Es un poco confuso lo que significan las variables que se accede en el cuerpo de la función . ¿También se cuenta la referencia a la instancia de la clase inclusiva?

He visto el tema que está haciendo referencia en su pregunta, pero parece que está obsoleto por ahora. He encontrado más información actualizada aquí :

La expresión Lambda o la función anónima guardan una referencia implícita de la clase inclusiva

Por lo tanto, desafortunadamente, parece que los lambdas de Kotlin tienen los mismos problemas que las clases internas anónimas de Java.

¿Por qué las clases anónimas internas son malas?

Desde las especificaciones de Java :

Una instancia i de una clase C interna directa de una clase O está asociada a una instancia de O, conocida como la instancia inmediata de i. La instancia de inclusión inmediata de un objeto, si la hay, se determina cuando se crea el objeto

Lo que esto significa es que la clase anónima siempre tendrá una referencia implícita a la instancia de la clase enclosing. Y puesto que la referencia está implícita no hay manera de deshacerse de ella.

Mira el ejemplo trivial

 public class YourActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new Thread(new Runnable() { // the inner class will keep the implicit reference to the outer activity @Override public void run() { // long-running task } }).start(); } } 

Como puede ver, en este caso se producirá la pérdida de memoria hasta que se ejecute la tarea de larga ejecución. Una solución para esto es utilizar clase anidada estática.

Dado que Kotlin's lambdas no inline de Kotlin's contienen la referencia a la instancia de la clase enclosing, tienen problemas similares con respecto a las pérdidas de memoria.

Bonus: Comparación rápida con otras implementaciones Lambda

Java 8 Lambdas

Sintaxis:

  • Declare SAM (solo método abstracto) interfaz

     interface Runnable { void run(); } 
  • Utilice esta interfaz como un tipo para un lambda

     public void canTakeLambda(Runnable r) { ... } 
  • Pase su lambda

     canTakeLambda(() -> System.out.println("Do work in lambda...")); 

Problemas de fugas de memoria: Como se indica en las especificaciones :

Las referencias a esto – incluyendo referencias implícitas a través de referencias de campo no calificadas o invocaciones de métodos – son, esencialmente, referencias a una variable local final. Los cuerpos lambda que contienen tales referencias capturan el ejemplo apropiado de esto. En otros casos, ninguna referencia a esto es retenida por el objeto.

En pocas palabras, si no usas ningún campo / método de la clase que lo incluye, no hay una referencia implícita a this como en el caso de las clases anónimas.

Retrolambda

De los documentos

Las expresiones Lambda son convertidas en clases internas anónimas. Esto incluye la optimización del uso de una instancia singleton para las expresiones lambda sin estado para evitar la asignación repetida de objetos.

Supongo que es autoexplicativo.

Rápido de Apple

Sintaxis:

  • La declaración es similar a Kotlin, en Swift los lambdas se llaman cierres:

     func someFunctionThatTakesAClosure(closure: (String) -> Void) {} 
  • Pasar el cierre

     someFunctionThatTakesAClosure { print($0) } 

    Aquí, $0 refiere al primer argumento String del cierre. Esto corresponde a it en Kotlin. Nota: A diferencia de Kotlin, en Swift también podemos referirnos a otros argumentos como $1 , $2 etc.

Problemas de fugas de memoria:

En Swift, al igual que en Java 8, el cierre captura una referencia fuerte a self ( this en Java y Kotlin) sólo si accede a una propiedad de la instancia, como self.someProperty , o si el cierre llama a un método en la instancia , Como self.someMethod() .

También los desarrolladores pueden especificar fácilmente que sólo quieren capturar la referencia débil:

  someFunctionThatTakesAClosure { [weak self] in print($0) } 

Ojalá fuera posible en Kotlin también 🙂

Las fugas de memoria ocurren cuando algún objeto que debe ser removido porque no es necesario ya no puede ser eliminado porque algo que tiene una vida útil más larga tiene una referencia a este objeto. El ejemplo más sencillo es almacenar la referencia a la Activity en la variable static (estoy hablando desde la perspectiva de Java, pero es similar en Kotlin): después de que el usuario ha hecho clic en el botón "Atrás", la Activity ya no es necesaria, pero sí Se mantendrá en la memoria sin embargo – porque alguna variable estática todavía apunta a esta actividad.
Ahora, en su ejemplo, no está asignando su Activity a alguna variable static , no hay ningún object de Kotlin involucrado que pueda impedir que su Activity sea ​​recolectada por la basura – todos los objetos involucrados en su código tienen aproximadamente la misma vida, lo que significa No habrá fugas de memoria.

PS He refrescado mis recuerdos en la implementación de Kotlin de los lambdas: en el caso del controlador de clics negativo, no estás haciendo referencia al ámbito externo, por lo que el compilador creará una instancia única del oyente de clics que se reutilizará en todos los Hace clic en este botón. En el caso del oyente de clic de botón positivo, estás haciendo referencia al ámbito externo ( this@MainActivity ), por lo que en este caso Kotlin creará una nueva instancia de la clase anónima cada vez que crees un diálogo (y esta instancia tendrá La referencia a la clase externa, MainActivity ), por lo que el comportamiento es exactamente el mismo que si hubiera escrito este código en Java.

  • Comparar archivos heap dump (HPROF)
  • Memory Leak en Crashlytics Android
  • Reciclaje Bitmap no libera memoria
  • Android Weak Referencia de la clase interna
  • Bitmap, Bitmap.recycle (), WeakReferences y Garbage Collection
  • ¿Cómo estática clase interna con una WeakReference a la clase externa puede evitar fugas de memoria de Android? Necesito un ejemplo
  • ¿Es esta una forma válida de mantener una referencia estática y Actividad / Contexto? ¿Por qué no debo hacer esto?
  • Interpretación de resultados MAT para la pérdida de memoria android
  • ¿Se filtra siempre la primera instancia de MapActivity?
  • Anónimo Oyente de la solicitud de volley causando pérdida de memoria
  • RuntimeException: tipo de letra nativo no se puede hacer o pérdida de memoria para personalizado TextView carga de fuente
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.