Saltar al contenido principal

Referencias en memoria

La siguiente sentencia es de asignación de tamaño de un array:

edad = new int[10];

Construye un array de elementos de tipo int y se le asigna a la variable edad.

Cómo se reserva espacio de memoria para un array

El operador new en Java se utiliza para crear nuevas instancias reservando espacio en memoria para dicha instancia. Es uno de los operadores más importantes de Java.

Veamos cómo funciona el operador new en el caso de los arrays.

  1. Primero calcula el tamaño físico del array, es decir, el número de bytes que ocupa el array. Se obtiene multiplicando el tamaño del array por el tamaño del tipo de dato. Por ejemplo, la variable anterior tiene 10 elementos y cada elemento (int) ocupa 32 bits (4 bytes). Por lo tanto, el array ocupa 4 · 10 = 40 bytes en memoria.

  2. Conociendo el tamaño del array, se busca en memoria un hueco libre con tamaño suficiente para guardar todos los elementos del array consecutivamente.

  3. Reserva la memoria necesaria para almacenar la tabla y la marca como memoria ocupada.

  4. Para finalizar recorre todos los elementos inicializándolos. Sabemos que cada 4 bytes desde la primera posición hay un elemento del array.

  5. El siguiente paso es asignarle a la variable correspondiente ese espacio. Java dispone de un mecanismo para indicar dónde está el array en memoria. Cada posición de memoria tiene una dirección. En Java cada dirección se denomina referencia. La forma de que la variable sepa dónde está el array en memoria es asignándole la referencia de la primera posición que ocupa. Las variables de arrays lo que realmente almacenan es la referencia. En este caso la variable edad almacenará el valor 0x00000723 (valor en hexadecimal).

Las referencias también son conocidas en otros lenguajes y en Java como punteros.

Acceso a un elemento de un array

El acceso en memoria de un elemento de un array en Java se realiza utilizando el índice del array. Cada elemento de un array está almacenado en posiciones de memoria contiguas. Para acceder a un elemento en concreto, el índice se multiplica por el tamaño del tipo de dato (en el caso de int, que ocupa 4 bytes), y después se accede a la dirección de memoria correspondiente.

La fórmula para calcular esta dirección de memoria es:

direccioˊn_elemento=direccioˊn_base+ıˊndice_elementotaman~o_del_elementotaman~o_direccioˊn_memoriadirección\_elemento = dirección\_base + \dfrac{índice\_elemento \cdot tamaño\_del\_elemento }{tamaño\_dirección\_memoria}

Para el ejemplo anterior, edad[0], está en la posición de memoria base del array. Para acceder al segundo elemento (edad[1]), Java calcula la posición de memoria como:

direccioˊn_elemento=direccioˊn_base+1taman~o_del_elementotaman~o_direccioˊn_memoriadirección\_elemento = dirección\_base + \dfrac{1 \cdot tamaño\_del\_elemento }{tamaño\_dirección\_memoria}

Tamaño de las direcciones de memoria

En Java, el tamaño de una dirección de memoria depende de la arquitectura de la máquina virtual de Java (JVM) y del sistema operativo en el que se ejecute el programa. Generalmente, hay dos tipos de arquitectura:

  1. Arquitectura de 32 bits: Las direcciones de memoria tienen un tamaño de 4 bytes (32 bits).
  2. Arquitectura de 64 bits: Las direcciones de memoria tienen un tamaño de 8 bytes (64 bits).

Suponiendo que:

  • Cada dirección de memoria ocupa 4 bytes (arquitectura de 32 bits).
  • El tamaño de un int es 4 bytes.

El segundo elemento del array estaría en la dirección:

direccioˊn_elemento_2=direccioˊn_base+132bits32bitsdirección\_elemento\_2 = dirección\_base + \dfrac{1 \cdot 32 \, bits }{32 \, bits}

El tercero estaría:

direccioˊn_elemento_2=direccioˊn_base+232bits32bitsdirección\_elemento\_2 = dirección\_base + \dfrac{2 \cdot 32 \, bits }{32 \, bits}

Y así sucesivamente.

Por lo tanto, los valores se encontrarían en las siguientes posiciones:

Índice de edadDirección de memoria
00x00000723
10x00000724
20x00000725
30x00000726
40x00000727
50x00000728
60x00000729
70x00000730
80x00000731
90x00000732

Este proceso se realiza automáticamente, por lo que al acceder a un elemento mediante array[indice], Java realiza el cálculo de la dirección de memoria para acceder al valor correcto de forma eficiente.

Por ejemplo:

int t[] = new int[10];
System.out.println(t);

En las líneas anteriores se obtiene la referencia a la dirección de memoria que guarda la variable. Las referencias cambian en cada ejecución, dependiendo de la ocupación en memoria.

Cambios de referencia

En Java, cuando cambias la referencia de un array a otro, simplemente haces que la variable que referencia al primer array apunte al segundo. Después de cambiar la referencia, ambas variables estarán referenciando al mismo array en memoria.

Así, cualquier cambio realizado en uno de los arrays se reflejará en el otro, porque estarán apuntando al mismo objeto.

Aquí tienes un ejemplo para ilustrar esto:

public class Main {
public static void main(String[] args) {
// Creamos el primer array con algunos elementos
int[] array1 = {1, 2, 3};

// Creamos el segundo array con otros elementos
int[] array2 = {10, 20, 30};

/*
Mostramos los elementos del primer *array* antes de cambiar la referencia.
Salida: 1 2 3
*/
System.out.print("Contenido de array1 antes de cambiar la referencia: ");
for (int i = 0; i < array1.length; i++) {
System.out.print(array1[i] + " ");
}
System.out.println();

/*
Mostramos los elementos del segundo *array* antes de cambiar la referencia.
Salida: 10 20 30
*/
System.out.print("Contenido de array2 antes de cambiar la referencia: ");
for (int i = 0; i < array2.length; i++) {
System.out.print(array2[i] + " ");
}
System.out.println();

// Cambiamos la referencia de array1 para que apunte a array2
array1 = array2;

/*
Ahora array1 y array2 apuntan al mismo array en memoria
Salida: 10 20 30
*/
System.out.print("Contenido de array1 después de cambiar la referencia: ");
for (int i = 0; i < array1.length; i++) {
System.out.print(array1[i] + " ");
}
System.out.println();

// Cambiamos un elemento en array1 para ver el efecto sobre array2
array1[0] = 100;

/*
Mostramos los contenidos de ambos arrays después del cambio. La salida es la misma.
Salida: 100 20 30
*/
System.out.print("Contenido de array1 después de modificar un elemento: ");
for (int i = 0; i < array1.length; i++) {
System.out.print(array1[i] + " ");
}
System.out.println();

System.out.print("Contenido de array2 después de modificar un elemento: ");
for (int i = 0; i < array2.length; i++) {
System.out.print(array2[i] + " ");
}
System.out.println();
}
}
  1. Creación de los arrays: Primero, definimos array1 y array2 con distintos valores.

  2. Mostrar los valores: Antes de cambiar la referencia, mostramos los valores iniciales de ambos arrays .

  3. Cambio de referencia: Cuando hacemos array1 = array2;, array1 comienza a apuntar al mismo lugar en memoria que array2.

  4. Modificación de elementos: Cambiamos el valor del primer elemento en array1. Dado que ahora ambos arrays apuntan al mismo objeto, verás que array2 también refleja el cambio.

  5. Mostrar los resultados: Ambos arrays mostrarán los mismos valores después del cambio de referencia y la modificación, lo que confirma que ambos apuntan al mismo array en memoria.

Recolector de basura

Cuando haces que array1 apunte a array2, la referencia original de array1 queda sin ninguna referencia, a menos que otro objeto esté apuntando a ella. En Java, esto significa que los datos originales que estaban en array1 quedan inaccesibles y pueden ser eliminados por el recolector de basura (garbage collector) de Java, ya que no hay ninguna referencia que apunte a ellos.

El recolector de basura, si detecta que ya no existen referencias a unos datos, Java considera que esos datos ya no son necesarios y libera la memoria que ocupaban para que pueda ser usada por otros datos en el futuro.