Ahora que todo nuestro código
se ejecuta rápido y genial, hablemos
un poco más acerca de la memoria y cómo
afecta el rendimiento en nuestro sistema.
Muchos lenguajes de programación
conocidos por ser cercanos al hardware,
o más bien, de alto desempeño
como C, C++
y Fortran, los programadores suelen tener
que administrar la memoria ellos mismos.
Los programadores son los responsables
de asignar un bloque de memoria
y, posteriormente, desasignarlo
cuando han terminado de utilizarlo,
dado que uno mismo define cuándo y
cuánta memoria asignar, toda la
calidad de la administración de memoria
depende de tus habilidades y efectividad.
Es mucha responsabilidad.
Y la realidad es que los programadores
no siempre son los mejores en controlar
todos esos trocitos de memoria.
Piénsalo, el desarrollo
de productos es laborioso y
agitado, y muchas veces la memoria
acaba no siendo liberada correctamente.
Esos bloques de memoria sin liberar
son llamados fugas de memoria, y andan
por ahí acaparando recursos
que podrías utilizar en otra parte.
Para reducir este caos, estrés, e incluso
grandes pérdidas monetarias,
causadas por fugas de memoria, se crearon
los lenguajes de memoria administrada.
Los tiempos de ejecución de estos
lenguajes rastrean las asignaciones y
liberan memoria al sistema
cuando ya no le hace falta
a la aplicación en sí misma. Sin ninguna
intervención por parte del programador.
Este arte, o más bien ciencia,
de recuperar memoria en un ambiente
de memoria administrada, se conoce como
"recolección de basura", el concepto
fue creado en 1959 por John McCarthy para
resolver problemas en el lenguaje LISP.
Los principios básicos de recolección
de basura son: número uno,
encontrar objetos de datos en un programa
al que no se puede acceder en el futuro,
por ejemplo, cualquier memoria que ya
no esté referida por el código.
Y número dos, recuperar los recursos
utilizados por dichos objetos.
El concepto es simple en teoría,
pero se vuelve bastante complejo
cuando tienes 2 millones de líneas
de código y cuatro gigas de asignaciones.
Piénsalo, la recolección de basura
puede ser realmente irritante,
si tienes unas 20.000 asignaciones
en tu programa ahora mismo,
¿cuales ya no se necesitan?
O mejor, ¿cuándo debes ejecutar un evento
de recolección de basura
para liberar memoria sin utilizar?
Éstas preguntas son realmente difíciles y
afortunadamente, hemos tenido unos
50 años de innovación para mejorarlas.
Por eso el recolector de basura
en el tiempo de ejecución de Android
es bastante más sofisticado que
la propuesta original de McCarthy.
Ha sido creado para ser tan rápido y
no invasivo como sea posible.
En efecto, las pilas de memoria
en los tiempos de ejecución de Android
están segmentadas en espacios
basado en el tipo de asignación y
en cómo el sistema puede organizar mejor
las asignaciones para futuros eventos GC.
Cuando se asigna un nuevo objeto,
estas características son tomadas en
cuenta para ajustarse a los espacios
más adecuados, dependiendo de la versión
del tiempo de ejecución Android que uses.
Y aquí viene lo importante:
cada espacio tiene un tamaño específico.
A medida que se asignan objetos, se van
se van vigilando los tamaños combinados
y cuando el tamaño empieza a crecer, el
sistema tendrá que ejecutar un evento
de recolección de basura, intentando
liberar memoria para futuras asignaciones.
Vale destacar que los eventos de GC
se comportan de forma diferente
dependiendo de cuál tiempo
de ejecución de Android se utilice.
Por ejemplo, en Dalvik, muchos eventos
de GC detienen los eventos mundiales,
así, cualquier código administrado
que se esté ejecutando se detiene
hasta completar la operación.
Lo que genera problemas cuando los GC
tardan más de lo normal, o cuando
ocurren muchos al mismo tiempo.
dado que van a consumir parte
significativa de tu tiempo.
y, seamos claros,
los ingenieros de Android han invertido
mucho tiempo en asegurarse de que
estos eventos sean lo más rápidos posible
para evitar interrupciones.
Dicho esto, pueden causar problemas en el
desempeño de aplicaciones en tu código.
En primer lugar, entiende que, mientras
más tiempo pase tu app haciendo GC
en cierto marco, menos tiempo tendrá para
el resto de la lógica que necesita para
mantenerte bajo la barrera de
renderización de 16 milisegundos.
Así que, si tienes muchos GC, o
algunos muy largos que ocurren uno
tras otro, eso podría ubicar tu tiempo de
procesamiento de cuadros por sobre
los 16 milisegundos
que es la barrera de renderización, lo
que causaría ralentización o fallas
a tus usuarios.
En segundo lugar, entiende que el flujo
del código puede estar haciendo cosas
que obliguen a eventos GC más frecuentes
o más largos de lo normal.
Por ejemplo, si estás asignando muchos
objetos en la parte interior
de un lazo que se ejecuta durante largo
tiempo, contaminarás
tu pila de memoria con muchos objetos
y acabarás generando muchos GC
rápidamente, debido a ésta
presión adicional sobre la memoria.
Incluso considerando que estamos en
un ambiente de memoria administrada,
pueden ocurrir fugas de memoria.
Aunque no es tan fácil crearlas
como en otros lenguajes.
Estas fugas pueden contaminar tu pila
con objetos que no se liberan
durante un evento GC, reduciendo
efectivamente el espacio utilizable y
obligando a que ocurran
más eventos GC con regularidad.
Así que eso es,
si quieres reducir la cantidad de GC
que ocurren en un cierto plazo,
debes enfocarte en optimizar
el uso de memoria de las apps.
Desde una perspectiva de código, puede
ser difícil detectar de dónde
salen éstos problemas, pero
afortunadamente, el Android SDK pone
un juego de poderosas herramientas
a tu disposición.
Veamos.