Comportamiento en memoria al crear Strings

Miguel Rodriguez
Pragma
Published in
5 min readAug 9, 2024

A medida que nuestras aplicaciones en Java evolucionan y se expanden en funcionalidades, la eficiencia en el uso de recursos se convierte en un aspecto crítico para asegurar el éxito y la durabilidad del proyecto. La atención dedicada a estos aspectos toma un rol fundamental en el desarrollo de software.

En Pragma nos esforzamos por siempre desarrollar código de calidad que sea legible y mantenible; es por eso que en esta oportunidad y en pro de #SaberMasParaResolverMejor te compartimos este artículo que tiene el objetivo de enseñar el comportamiento de la memoria a la hora de crear Strings.

Definición y alcance del problema

La optimización de la memoria es crucial para el desempeño de las aplicaciones Java. Comprender cómo funcionan el Stack, el Heap y el String Pool es fundamental para lograr este objetivo.

Una gestión eficiente de la memoria garantiza que nuestras aplicaciones no solo sean rápidas, sino también escalables y mantenibles.

Este artículo tiene como objetivo brindar una comprensión profunda de los conceptos de Stack, Heap y String Pool, y cómo estos se relacionan con la creación y gestión de objetos String en Java.

Contenido

Exploremos cómo el String Pool, el Stack y el Heap interactúan en la gestión de la memoria cuando utilizamos Strings. Comprender estas estructuras no solo asegura el correcto funcionamiento de nuestras aplicaciones, sino que también optimiza el uso de la memoria para alcanzar un rendimiento óptimo.

1. Heap y Stack

Para profundizar en este tema, primero debemos entender los conceptos de Stack y Heap. En resumen, en Java, el Heap es la memoria donde se almacenan los objetos, mientras que el Stack es la memoria utilizada para almacenar la ejecución de métodos y variables.

Para ilustrar esto, veamos un ejemplo. Primero, crearemos una clase Person con sus atributos:

public class Person {

private String name;
private String lastName;

public Person(String name, String lastName) {
this.name = name;
this.lastName = lastName;
}

}

A continuación, crearemos dos instancias de esta clase:

public class Main {
public static void main(String[] args) {

Person person1 = new Person("Miguel", "Rodriguez");
Person person2 = new Person("Miguel", "Rodriguez");

}
}

Al ejecutar nuestra aplicación, se agrega la ejecución del método ‘main’ en una sección de memoria del Stack. Como se mencionó anteriormente, el Stack es responsable de almacenar las ejecuciones de métodos y las variables.

Posteriormente, las variables ‘person1’ y ‘person2’ se almacenan en el Stack, mientras que los objetos creados se almacenan en el Heap.

En la imagen anterior, podemos observar que el comportamiento del Stack al agregar elementos es similar al de una pila, y que los punteros de las variables apuntan a los valores correspondientes en el Heap.

Ahora que hemos comprendido los conceptos de Stack y Heap, procederemos a comparar estos objetos utilizando el operador ==.

System.out.println(person1 == person2); // false

El resultado de esta operación es false, porque el operador “==” verifica si los punteros de ambas variables apuntan al mismo objeto. Aunque ambos objetos tengan exactamente los mismos valores en sus atributos, en memoria se registran como dos instancias nuevas e independientes.

2. String Pool:

Java maneja de manera diferente el almacenamiento de variables con literales de String. Aunque estos literales se almacenan en el Heap, lo hacen en una subsección especial llamada String Pool, con el objetivo de evitar la duplicación de los literales en memoria.

Para entender mejor este concepto, veamos el siguiente ejemplo:

Primero, crearemos dos Strings literales iguales.

public static void main(String[] args) {

String greeting1 = "Hello world!!";
String greeting2 = "Hello world!!";

}

A continuación, observemos cómo se almacenan estos Strings en memoria:

Al declarar greeting1, su valor literal se almacena en el String Pool. Cuando se declara greeting2 con el mismo literal, se obtiene una referencia al mismo objeto en el Pool, debido a la interning de Strings. Por tanto, greeting1 == greeting2 es true.

System.out.println(greeting1 == greeting2); // true 

3. Creación de un new String

Si bien los literales de cadena se almacenan en el String Pool, la creación de objetos String con new resulta en instancias separadas en el heap. En el siguiente ejemplo, compararemos ambas técnicas para evidenciar este comportamiento

public static void main(String[] args) {

String greeting1 = "Hello world!!";
String greeting2 = new String("Hello world!!");

System.out.println(greeting1 == greeting2); // false
}

Para una mejor comprensión del resultado, vamos a visualizar la distribución en memoria de las variables y sus valores asociados

La imagen muestra claramente que el objeto creado con new se aloja en el heap, a diferencia de los literales que se almacenan en el String Pool. Esta distinción explica por qué la comparación devuelve false

4. Garbage Collector

El Garbage Collector (GC) es como un “recolector de basura” automático en Java. Se encarga de liberar la memoria ocupada por objetos que ya no son utilizados por nuestro programa. Cuando un objeto no tiene ninguna referencia que apunte a él, el GC lo marca para su eliminación y recupera el espacio de memoria que ocupaba.

Veamos qué ocurre cuando creamos un String usando new

public static void main(String[] args) {

String greeting = new String("Hello world!!");

}

Analicemos qué sucedió en el heap

Como se observa en la imagen, se crearon dos representaciones de la cadena ‘Hello world!!’ en memoria: una en el String Pool y otra en el heap. La variable greeting apunta al objeto en el heap. Aunque el literal en el String Pool no tiene una referencia directa desde la variable, el Garbage Collector de Java gestiona la liberación de la memoria utilizada por objetos no alcanzables. Sin embargo, la creación innecesaria de objetos puede aumentar el consumo de memoria y, en algunos casos, afectar ligeramente el rendimiento debido a la actividad del Garbage Collector.

Conclusión

Hagamos un repaso de lo que hicimos:

  • Aprendimos que el Stack es un área en memoria donde se almacenan las ejecuciones de los métodos y las variables en forma de pila; y que el Heap es un área en memoria donde se almacenan las instancias de los objetos.
  • Aprendimos que el String Pool es una optimización de Java que almacena de forma única las cadenas literales, mejorando el rendimiento y ahorrando memoria.
  • Aprendimos que crear objetos String con new puede llevar a duplicaciones en memoria. El Garbage Collector se encarga de liberar la memoria de los objetos no utilizados, sin embargo, esto puede impactar negativamente en el rendimiento y el consumo de memoria.

--

--

Miguel Rodriguez
Pragma
0 Followers
Writer for

Soy un ingeniero de sistemas y computación, con un enfoque en el área de desarrollo backend.