1. Introducción
En este artículo, exploraremos algunas preguntas sobre la gestión de memoria que con frecuencia surgen durante las entrevistas a desarrolladores Java. La gestión de memoria es un área que no muchos desarrolladores conocen bien. De hecho, los desarrolladores generalmente no tienen que lidiar directamente con este concepto, ya que la JVM se encarga de los detalles complicados. Sin embargo, los conceptos de gestión de memoria son comunes durante las entrevistas, así que profundicemos en ellos.
2. Preguntas
Q1. ¿Qué significa la afirmación “La memoria se gestiona en Java”?
La memoria es el recurso clave que una aplicación necesita para funcionar de manera efectiva y, como cualquier recurso, es limitada. Por lo tanto, su asignación y liberación a las aplicaciones o diferentes partes de una aplicación requieren un cuidado y consideración significativos. Sin embargo, en Java, un desarrollador no necesita asignar y liberar memoria explícitamente: la JVM, y más específicamente el recolector de basura (Garbage Collector), es responsable de manejar la asignación de memoria, para que el desarrollador no tenga que hacerlo.
Esto contrasta con lo que ocurre en lenguajes como C, donde un programador tiene acceso directo a la memoria y hace referencia literalmente a las celdas de memoria en su código, dejando mucho margen para pérdidas de memoria.
Q2. ¿Qué es la recolección de basura y cuáles son sus ventajas?
La recolección de basura es el proceso que examina la memoria del montón (heap), identifica qué objetos están en uso y cuáles no, y elimina los objetos no utilizados. Un objeto en uso, o un objeto referenciado, significa que alguna parte del programa mantiene un puntero hacia ese objeto. Por otro lado, un objeto no utilizado, o un objeto no referenciado, ya no está referenciado por ninguna parte del programa. Por lo tanto, la memoria utilizada por un objeto no referenciado puede ser recuperada.
La mayor ventaja de la recolección de basura es que elimina la carga de la asignación/liberación manual de memoria, permitiéndonos enfocarnos en resolver el problema en cuestión.
Q3. ¿Existen desventajas en la recolección de basura?
Sí. Cada vez que el recolector de basura se ejecuta, afecta el rendimiento de la aplicación. Esto se debe a que todos los demás hilos en la aplicación tienen que detenerse para permitir que el hilo del recolector de basura lleve a cabo su trabajo de manera efectiva.
Dependiendo de los requisitos de la aplicación, este puede ser un problema real inaceptable para el cliente. Sin embargo, este inconveniente puede reducirse o incluso eliminarse mediante una optimización hábil y ajuste del recolector de basura, utilizando diferentes algoritmos de GC.
Q4. ¿Qué significa el término “Stop-The-World”?
Cuando el hilo del recolector de basura está en ejecución, se detienen otros hilos, lo que significa que la aplicación se detiene momentáneamente. Esto es análogo a la limpieza de una casa, donde los ocupantes no tienen acceso hasta que se completa el proceso.
Dependiendo de las necesidades de una aplicación, la recolección de basura “stop-the-world” puede causar un congelamiento inaceptable. Por esto, es importante realizar un ajuste del recolector de basura y optimización de la JVM para que la congelación experimentada sea al menos aceptable.
Q5. ¿Qué son la pila (stack) y el montón (heap)? ¿Qué se almacena en cada una de estas estructuras de memoria y cómo se interrelacionan?
La pila (stack) es parte de la memoria que contiene información sobre las llamadas a métodos anidadas hasta la posición actual en el programa. También contiene todas las variables locales y referencias a objetos en el montón definidas en los métodos que se están ejecutando actualmente. Esta estructura permite que el tiempo de ejecución regrese desde el método sabiendo la dirección desde la que fue llamado y también borre todas las variables locales después de salir del método. Cada hilo tiene su propia pila.
Por otro lado, el montón (heap) es una gran área de memoria destinada a la asignación de objetos. Cuando se crea un objeto con la palabra clave new
, se asigna en el montón. Sin embargo, la referencia a este objeto vive en la pila.
Ejemplo de Pila y Montón
public class MemoriaDemo {
public static void main(String[] args) {
String mensaje = new String("Hola, Java!");
System.out.println(mensaje);
}
}
En este caso, mensaje
se almacena en la pila, mientras que el objeto de tipo String se crea y se almacena en el montón.
Q6. ¿Qué es la recolección de basura generacional y qué la hace popular?
La recolección de basura generacional se puede definir como la estrategia utilizada por el recolector de basura donde el montón se divide en secciones llamadas generaciones, cada una de las cuales contiene objetos de acuerdo a su “edad” en el montón. La recolección de basura generacional es popular debido a que la mayoría de los objetos son de corta duración, lo que significa que pasan poco tiempo en el montón antes de ser recolectados. Con esta estrategia, se optimizan los ciclos de recolección de basura, minimizando el tiempo que el recolector pasa examinando objetos que probablemente no sobrevivirán.
Q7. Describe en detalle cómo funciona la recolección de basura generacional.
Para entender cómo funciona la recolección de basura generacional, es importante recordar cómo está estructurado el montón de Java para facilitar este proceso. El montón se divide en varias generaciones: la generación joven, la generación vieja y la generación permanente.
- La generación joven alberga la mayoría de los objetos recién creados. A menudo, los objetos en esta parte del montón son de vida corta y se vuelven elegibles para la recolección de basura rápidamente. Cuando se llena, se desencadena una colección menor.
- La generación vieja contiene objetos que han vivido en memoria más tiempo que un cierto umbral de “edad”.
- La generación permanente intacta, o PermGen, incluye metadatos necesarios para que la JVM describa las clases y métodos utilizados por la aplicación.
El ciclo de recolección de basura comienza con la asignación de nuevos objetos en la generación joven. A medida que los objetos sobreviven a más ciclos de recolección de basura, son promocionados a la generación vieja.
Q8. ¿Cuándo se vuelve un objeto elegible para la recolección de basura?
Un objeto se vuelve elegible para la recolección de basura si no es accesible desde ninguno de los hilos activos o por cualquier referencia estática. Por ejemplo:
class A {
B b = new B();
}
class B {
A a;
}
Si una instancia de A
se establece en null, entonces B
también se vuelve elegible para la recolección de basura.
Q9. ¿Cómo se puede disparar la recolección de basura desde el código Java?
Como programador Java, no puedes forzar la recolección de basura. Sin embargo, puedes solicitarla mediante el uso de System.gc()
o Runtime.getRuntime().gc()
, pero esto no garantiza que la recolección se realizará.
Q10. ¿Qué sucede cuando no hay suficiente espacio en el montón para almacenar nuevos objetos?
Si no hay suficiente espacio en el montón para crear un nuevo objeto, la JVM lanzará una excepción OutOfMemoryError. Esto indica que no hay espacio suficiente en el montón para satisfacer la solicitud de creación del objeto.
Q11. ¿Es posible “resucitar” un objeto que se volvió elegible para la recolección de basura?
Efectivamente, un objeto elegible para la recolección de basura puede “resucitarse” dentro del método finalize()
. Sin embargo, puede resucitarse solo una vez, ya que después se marcará como finalizado. Este patrón es poco común y se considera una mala práctica.
Q12. Describe las referencias fuertes, débiles, suaves y fantasmales y su papel en la recolección de basura.
Java ofrece cuatro tipos de referencias, cada una con un comportamiento diferente con respecto a la recolección de basura:
- Referencia fuerte: Esta es la referencia más común, y cualquier objeto referenciado de esta manera nunca será recolectado.
StringBuilder sb = new StringBuilder();
WeakReference<StringBuilder> weakRef = new WeakReference<>(sb);
SoftReference<StringBuilder> softRef = new SoftReference<>(sb);
Q13. Supongamos que tenemos una referencia circular entre dos objetos. ¿Podría una de estas referencias ser elegible para la recolección de basura y por qué?
Sí, un par de objetos con una referencia circular puede volverse elegible para la recolección de basura si no son accesibles desde ninguna raíz de la recolección de basura (como las variables locales de un hilo activo).
Q14. ¿Cómo se representan las cadenas en la memoria?
Las instancias de String en Java son objetos que contienen dos campos: un campo char[] value
y un campo int hash
. Las cadenas son inmutables, lo que significa que no puedes modificar la matriz subyacente. Además, las cadenas constantes se almacenan en un pool de cadenas.
Q15. ¿Qué es un StringBuilder y cuáles son sus casos de uso?
StringBuilder permite manipular secuencias de caracteres de forma mutable. A diferencia de la clase String, un StringBuilder no crea nuevos objetos en cada modificación, lo cual es más eficiente para operaciones repetitivas.
StringBuilder sb = new StringBuilder();
sb.append("Hola").append(", Java!");
System.out.println(sb.toString());
3. Conclusión
En este artículo, hemos cubierto algunas de las preguntas más comunes sobre la gestión de memoria que frecuentemente aparecen en las entrevistas para ingenieros Java. Las preguntas sobre gestión de memoria suelen ser formuladas a candidatos para puestos de desarrollador Senior, ya que se espera que tengan experiencia en la construcción de aplicaciones no triviales que, con frecuencia, enfrenta problemas de memoria.
Aunque esta no es una lista exhaustiva de preguntas, pretendemos que sea un punto de partida para investigaciones adicionales. Desde nuestro equipo, te deseamos mucho éxito en tus próximas entrevistas.