-
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.