Secuencia Completa de Inicio de JVM y Asignación de Memoria

Este documento describe el flujo completo desde el inicio de la Máquina Virtual Java (JVM) hasta la asignación de memoria, detallando la relación con las áreas de memoria correspondientes.

El proceso se puede visualizar de la siguiente manera:


┌─────────────────────────────────────────────────────────────┐
│                     Flujo Completo de Inicio de JVM           │
├─────────────────────────────────────────────────────────────┤
│                                                                │
│  1. Asignación de Memoria para el Proceso JVM                  │
│      ↓                                                         │
│  2. Inicialización de Áreas de Datos en Tiempo de Ejecución (en orden fijo) │
│      ├─ a. Registro PC (asignado al crear hilos)              │
│      ├─ b. Espacio de Métodos / Metaspace (al inicio de JVM)    │
│      ├─ c. Heap (al inicio de JVM)                            │
│      ├─ d. Pila (asignado al crear hilos)                      │
│      └─ e. Pila de Métodos Nativos (asignado al crear hilos)    │
│      ↓                                                         │
│  3. Proceso de Carga de Clases (activado bajo demanda)         │
│      ├─ a. Carga (Loading) → Espacio de Métodos / Metaspace    │
│      ├─ b. Verificación (Verification)                          │
│      ├─ c. Preparación (Preparation) → Espacio de Métodos / Metaspace │
│      ├─ d. Resolución (Resolution)                              │
│      └─ e. Inicialización (Initialization)                     │
│      ↓                                                         │
│  4. Creación de Instancias de Objeto → Heap                    │
│      ↓                                                         │
│  5. Ejecución de Métodos → Pila                                  │
└─────────────────────────────────────────────────────────────┘

Secuencia Detallada y Correspondencia con Áreas de Memoria

Fase 1: Orden de Inicialización de Áreas de Memoria al Inicio de la JVM

El inicio de la JVM implica la asignación y configuración de sus áreas de memoria principales:


public class JVMBootSequence {
    /**
     * Secuencia 1: Inicio del proceso JVM, solicitando memoria al SO.
     *      Sistema Operativo → Espacio de Memoria del Proceso JVM
     */

    /**
     * Secuencia 2: Inicialización del Espacio de Métodos / Metaspace (primero).
     *      Controlado por -XX:PermSize/-XX:MetaspaceSize.
     *      Almacena: Plantillas de información de clases.
     */

    /**
     * Secuencia 3: Inicialización del Heap (segundo).
     *      Controlado por -Xms/-Xmx.
     *      Inicialmente vacío, sin objetos.
     *      Contiene:
     *      ├─ Generación Joven (Young Generation)
     *      ├─ Generación Vieja (Old Generation)
     *      └─ (En JDK 8+) El pool de strings literales también se inicializa aquí.
     */

    /**
     * Secuencia 4: Creación del hilo principal, inicialización de áreas privadas del hilo (último).
     *      Contiene:
     *      ├─ Registro de Contador de Programa (Program Counter Register)
     *      ├─ Pila (Stack) - Tamaño controlado por -Xss
     *      └─ Pila de Métodos Nativos (Native Method Stack)
     */
}

Fase 2: Orden de Asignación de Memoria en el Proceso de Carga de Clases (¡Core!)

La carga de clases es un proceso fundamental que ocurre bajo demanda y se relaciona directamente con la asignación de memoria en el Espacio de Métodos (Metaspace):


public class ClassLoadingSequence {
    // ========== Primer Paso: Momentos que Disparan la Carga de Clases ==========
    // 1. Palabra clave 'new' (más común)
    // 2. Acceso a miembros estáticos de una clase
    // 3. Invocaciones de reflexión
    // 4. La subclase se carga, la superclase se carga primero
    // 5. Clase principal (que contiene el método main)

    // ========== Segundo Paso: Asignación de Memoria Durante la Carga ==========

    /**
     * 1. Fase de Carga (Loading)
     * Asignación de Memoria: Espacio de Métodos / Metaspace
     * Operaciones:
     *   - Localizar el archivo .class
     *   - Leer el flujo binario
     *   - Crear la estructura del objeto Class en el Espacio de Métodos
     *   - Crear una instancia de java.lang.Class en el Heap (como punto de acceso)
     */

    /**
     * 2. Fase de Verificación (Verification)
     * Memoria: Principalmente en la Pila para operaciones de verificación.
     * Operaciones: Validar la corrección del bytecode.
     */

    /**
     * 3. Fase de Preparación (Preparation) - ¡Clave!
     * Asignación de Memoria: Varía según la versión de JDK.
     *
     * JDK 7 y anteriores (Espacio de Métodos / PermGen):
     *   - Variables estáticas → Espacio de Métodos
     *   - Constantes → Pool de Constantes en Tiempo de Ejecución (dentro del Espacio de Métodos)
     *
     * JDK 8+ (Metaspace + Heap):
     *   - Variables estáticas → Heap
     *   - Constantes → Pool de Constantes en Tiempo de Ejecución en el Heap
     *   - Información de clase (Klass) → Metaspace
     *
     * Nota: En esta fase, solo se asignan "valores cero", no los valores iniciales del programa.
     */
    private static int staticInt;          // Fase de Preparación: valor = 0
    private static String staticString;    // Fase de Preparación: valor = null
    private static final int CONSTANT = 100; // Fase de Preparación: valor = 100 (constante especial)

    /**
     * 4. Fase de Resolución (Resolution)
     * Memoria: Espacio de Métodos / Metaspace
     * Operaciones: Convertir referencias simbólicas a referencias directas.
     */

    /**
     * 5. Fase de Inicialización (Initialization) - ¡Ejecución de Código!
     * Memoria: Pila (para ejecución) + Heap (para asignación de valores reales)
     * Operaciones:
     *   - Ejecutar bloques de código estático.
     *   - Asignar valores reales a variables estáticas.
     */
    static {
        // Ejecutado en la Pila, asignando al staticInt en el Heap.
        staticInt = 10;                    // Fase de Inicialización: valor = 10
        staticString = "init";            // El objeto String se crea en el Heap.
        System.out.println("Ejecución de bloque estático.");
    }
}

Secuencia de Asignación de Memoria al Crear Objetos

Una vez que una clase ha sido cargada, se pueden crear instancias de objetos:


public class ObjectCreationSequence {
    // Variables de instancia
    private int instanceVar = 5;          // Valor inicial
    private String instanceStr = "hello"; // Tipo de referencia
    private final int finalVar = 20;      // Constante

    // Bloque de código de instancia
    {
        System.out.println("Ejecución de bloque de instancia, instanceVar=" + instanceVar);
        instanceVar = 10;                  // Modifica el valor inicial
    }

    // Constructor
    public ObjectCreationSequence() {
        System.out.println("Ejecución de constructor, instanceVar=" + instanceVar);
        instanceVar = 15;                  // Modifica de nuevo
    }

    public static void main(String[] args) {
        /**
         * Secuencia de Asignación de Memoria al Crear un Objeto:
         *
         * 1. La instrucción 'new' se activa.
         *    ↓
         * 2. Se verifica si la clase ya ha sido cargada.
         *    ├─ Si no ha sido cargada → Se inicia el proceso de carga de clases.
         *    └─ Si ha sido cargada → Continúa.
         *    ↓
         * 3. Se asigna memoria para el objeto en el Heap.
         *    ├─ Se calcula el tamaño del objeto (incluyendo todas las variables de instancia).
         *    ├─ Se asigna espacio en el área Eden de la Generación Joven.
         *    └─ La memoria se inicializa a cero.
         *    ↓
         * 4. Se establece la información de la cabecera del objeto.
         *    ├─ Mark Word (hashCode, edad de GC, estado del bloqueo, etc.)
         *    ├─ Puntero de Tipo (apunta a la información de la clase en el Espacio de Métodos).
         *    └─ (Si es un array) Longitud del array.
         *    ↓
         * 5. Se ejecuta la inicialización de la instancia (¡en orden!).
         *    ├─ Primero: Inicialización directa de variables de instancia (instanceVar=5).
         *    ├─ Luego: Ejecución del bloque de código de instancia ({...}).
         *    └─ Finalmente: Ejecución del constructor (ObjectCreationSequence()).
         *    ↓
         * 6. Se devuelve la referencia del objeto a la variable local en la Pila.
         */

        // Dispara la creación del objeto
        ObjectCreationSequence obj = new ObjectCreationSequence();

        // Imprime el resultado final
        System.out.println("Valor final de instanceVar=" + obj.instanceVar);  // 15
    }
}

Secuencia de Asignación de Memoria al Ejecutar un Método

Cada llamada a un método resulta en la creación de un nuevo "frame" (marco) en la Pila:


public class MethodExecutionSequence {
    private static int staticCounter = 0;
    private int instanceCounter = 0;

    public void executeMethod(int param1, String param2) {
        /**
         * Secuencia de Asignación de Memoria al Invocar un Método:
         *
         * 1. Antes de la invocación del método:
         *    - Los valores de los parámetros ya están calculados (en el frame del llamador).
         *
         * 2. Creación de un nuevo Frame de Pila:
         *    ├─ Pila de Operandos (Operand Stack)
         *    ├─ Tabla de Variables Locales (Local Variables)
         *    └─ Enlace Dinámico, Dirección de Retorno, etc.
         *
         * 3. Transferencia de parámetros al nuevo Frame:
         *    - param1 → slot 0 de la Tabla de Variables Locales
         *    - param2 → slot 1 de la Tabla de Variables Locales
         *    - Referencia 'this' (para métodos no estáticos) → slot 2
         *
         * 4. Ejecución del cuerpo del método:
         *    - Las variables locales se asignan en la Pila.
         *    - La creación de nuevos objetos se asigna en el Heap.
         *    - Acceso a variables estáticas: a través del Espacio de Métodos para encontrar su ubicación en el Heap.
         *    - Acceso a variables de instancia: a través de la referencia 'this' para encontrar el objeto en el Heap.
         */

        // Entero local → Pila
        int localInt = 100;

        // Referencia local → Pila (la referencia), objeto en el Heap
        String localStr = new String("local");

        // Acceso a variable estática → A través del Espacio de Métodos para encontrar su ubicación en el Heap
        staticCounter++;

        // Acceso a variable de instancia → A través de 'this' para encontrar el objeto en el Heap
        instanceCounter++;

        // Invocación de método → Creación de un nuevo Frame de Pila
        helperMethod(localInt);
    }

    private void helperMethod(int value) {
        // Las llamadas recursivas crean continuamente nuevos Frames de Pila.
        if (value > 0) {
            helperMethod(value - 1);
        }
    }

    public static void main(String[] args) {
        /**
         * Secuencia de ejecución del método main:
         * 1. La JVM crea el hilo principal.
         * 2. Se crea un Frame de Pila para el método main.
         * 3. El parámetro 'args' se coloca en la Pila.
         * 4. Se ejecuta el cuerpo del método main.
         */

        MethodExecutionSequence obj = new MethodExecutionSequence();
        obj.executeMethod(10, "test");

        // Posible desbordamiento de pila (StackOverflowError)
        // obj.helperMethod(10000);
    }
}

Ejemplo Comprensivo: Secuencia Completa de Asignación de Memoria

Este ejemplo integra todos los conceptos de asignación de memoria discutidos:


public class CompleteMemorySequence {
    // ========== Nivel de Clase (Espacio de Métodos / Metaspace) ==========

    // 1. La información de la clase se carga en el Espacio de Métodos / Metaspace.
    // 2. La información del pool de constantes se carga.

    // ========== Miembros Estáticos (Fase de Preparación) ==========
    private static int staticInt;          // Preparación: 0 → Heap (JDK8+)
    private static Object staticObj;       // Preparación: null → Heap (JDK8+)
    private static final String CONSTANT = "CONST"; // Preparación: "CONST" → Pool de Constantes

    // ========== Bloque de Código Estático (Fase de Inicialización) ==========
    static {
        // Ejecución: Se ejecuta en la Pila, asignando valores a variables estáticas en el Heap.
        staticInt = 100;
        staticObj = new Object();  // Se crea un objeto Object en el Heap.
        System.out.println("Bloque Estático: staticInt=" + staticInt);
    }

    // ========== Miembros de Instancia (al Crear Objeto) ==========
    private int instanceInt = 50;          // Inicializado al crear el objeto.
    private String instanceStr;            // Por defecto null.
    private final int finalInt = 200;      // Constante.

    // ========== Bloque de Código de Instancia (al Crear Objeto) ==========
    {
        // Se ejecuta antes del constructor.
        instanceStr = "initialized";
        System.out.println("Bloque de Instancia: instanceInt=" + instanceInt);
    }

    // ========== Constructor ==========
    public CompleteMemorySequence() {
        // Se ejecuta al final.
        instanceInt = 300;
        System.out.println("Constructor: instanceInt=" + instanceInt);
    }

    // ========== Ejecución de Método ==========
    public void process() {
        // Se crea un Frame de Pila para el método process.
        int localVar = 500;                // Asignado en la Pila.
        Object localObj = new Object();    // Objeto creado en el Heap, referencia en la Pila.

        // Acceso a datos de diferentes áreas de memoria.
        System.out.println("Acceso a variable estática: " + staticInt);      // A través de Metaspace → Heap.
        System.out.println("Acceso a variable de instancia: " + instanceInt);    // A través de 'this' → Heap.
        System.out.println("Acceso a variable local: " + localVar);       // Acceso directo en la Pila.
    }

    // ========== Método main ==========
    public static void main(String[] args) {
        /**
         * Secuencia de Ejecución Completa:
         *
         * Fase 1: Carga de Clase (la primera vez que se usa)
         *   1. La clase CompleteMemorySequence se carga en el Espacio de Métodos / Metaspace.
         *   2. Fase de Preparación: Se asigna memoria para variables estáticas (Heap/Metaspace).
         *   3. Fase de Inicialización: Se ejecuta el bloque de código estático.
         *
         * Fase 2: Ejecución del método main
         *   1. Se crea la Pila para el hilo principal.
         *   2. Se crea un Frame de Pila para el método main.
         *   3. El parámetro 'args' se coloca en la Pila.
         *
         * Fase 3: Creación de Objeto
         *   1. La instrucción 'new' se activa.
         *   2. Se asigna memoria para el objeto en el Heap.
         *   3. Las variables de instancia se inicializan a cero.
         *   4. Se establece la cabecera del objeto.
         *   5. Se ejecuta el bloque de código de instancia.
         *   6. Se ejecuta el constructor.
         *   7. La referencia se devuelve a la Pila.
         *
         * Fase 4: Invocación de Método
         *   1. Se crea un nuevo Frame de Pila para el método process.
         *   2. Los parámetros y variables locales se asignan en la Pila.
         *   3. Se crea un nuevo objeto en el Heap.
         */

        System.out.println("=== Inicio de Ejecución ===");

        // Primera vez que se usa la clase, se dispara la carga de clases.
        System.out.println("CONSTANT=" + CONSTANT);

        // Creación del objeto.
        CompleteMemorySequence obj = new CompleteMemorySequence();

        // Invocación del método.
        obj.process();

        System.out.println("=== Fin de Ejecución ===");
    }
}

Tabla Resumen de Secuencia de Asignación de Memoria

Fase Condición de Activación Área de Memoria Principle Orden de Operación
Inicio de JVM Comando 'java' Todas las áreas Metaspace → Heap → Pila
Carga de Clase Primer uso de la clase Espacio de Métodos / Metaspace + Heap Carga → Verificación → Preparación → Resolución → Inicialización
Inicialización Estática Fase de inicialización de clase Pila (ejecución) + Heap (almacenamiento) Estática Padre → Estática Hijo
Creación de Objeto Palabra clave 'new' Heap (objeto) + Pila (referencia) Asignar memoria → Asignar cero → Bloque de instancia → Constructor
Invocación de Método Llamada a método Pila (Frame) Crear Frame → Parámetros en Pila → Variables locales
Creación de Array 'new' seguido de corchetes [] Heap Asignar memoria contigua → Asignar cero
Creación de String Literal / 'new String(...)' Heap (Pool de Strings / Objeto) Buscar en Pool → Si no existe, crear objeto

Código de Validación: Observar el Orden de Carga

Este ejemplo demuestra el orden de inicialización en una jerarquía de herencia:


public class LoadingOrderValidator {
    static class Parent {
        static String parentStatic = "Variable estática padre";

        static {
            System.out.println("Bloque estático padre: " + parentStatic);
        }

        String parentInstance = "Variable de instancia padre";

        {
            System.out.println("Bloque de instancia padre: " + parentInstance);
        }

        Parent() {
            System.out.println("Constructor padre");
        }
    }

    static class Child extends Parent {
        static String childStatic = "Variable estática hijo";

        static {
            System.out.println("Bloque estático hijo: " + childStatic);
        }

        String childInstance = "Variable de instancia hijo";

        {
            System.out.println("Bloque de instancia hijo: " + childInstance);
        }

        Child() {
            System.out.println("Constructor hijo");
        }
    }

    public static void main(String[] args) {
        System.out.println("=== Primer uso de la clase Child, se dispara la carga de clases ===");

        // Acceder a una variable estática dispara la carga de clases pero no la creación de objetos.
        System.out.println("Accediendo a variable estática: " + Child.childStatic);

        System.out.println("\n=== Creando un objeto Child ===");
        Child child = new Child();

        System.out.println("\n=== Accediendo de nuevo a la variable estática (clase ya cargada) ===");
        System.out.println("Acceso repetido: " + Child.childStatic);
    }
}

Resultado de la ejecución: ```

=== Primer uso de la clase Child, se dispara la carga de clases === Bloque estático padre: Variable estática padre Bloque estático hijo: Variable estática hijo Accediendo a variable estática: Variable estática hijo

=== Creando un objeto Child === Bloque de instancia padre: Variable de instancia padre Constructor padre Bloque de instancia hijo: Variable de instancia hijo Constructor hijo

=== Accediendo de nuevo a la variable estática (clase ya cargada) === Acceso repetido: Variable estática hijo


Conclusiones Clave
------------------

1. El orden de inicialización de las áreas de memoria es fijo: Metaspace → Heap → Pila.
2. La carga de clases es "perezosa": ocurre solo la primera vez que se utiliza una clase.
3. Lo estático precede a lo de instancia: La inicialización estática se completa durante la carga de la clase; la inicialización de instancia ocurre al crear el objeto.
4. La clase padre precede a la clase hija: En relaciones de herencia, la clase padre se inicializa primero.
5. Orden de ejecución de código: Bloque estático → Bloque de instancia → Constructor.
6. Diferencias entre versiones de JVM: En JDK 8 y posteriores, las variables estáticas residen en el Heap; en JDK 7 y anteriores, residían en el Espacio de Métodos (PermGen).

Resumen Clave: Métodos Estáticos y Asignación de Memoria
--------------------------------------------------------

### 1. Orden Temporal

  1. Inicio de JVM → Inicialización de áreas de memoria (Metaspace, Heap, Pila).
  2. Primera vez que se usa una clase → Carga de la clase en Metaspace.
  3. Variables estáticas → Asignación de memoria en la fase de preparación (Heap/Metaspace).
  4. Bloques de código estáticos → Ejecución en la fase de inicialización (en la Pila).
  5. Invocación de métodos estáticos → Creación de un Frame de Pila, ejecución de bytecode.
  6. Creación de objetos → Asignación en Heap, ejecución de código de inicialización.
  7. Invocación de métodos de instancia → Frame de Pila con referencia 'this'.

### 2. Asignación Espacial

// Metaspace: Información de clase + Bytecode de métodos (carga única). // Heap: Valores de variables estáticas + Instancias de objetos (asignación en tiempo de ejecución). // Pila: Frames de métodos + Variables locales (asignación al invocar). // Pool de Constantes: Literales + Referencias simbólicas (durante la carga de la clase).


### 3. Características de los Métodos Estáticos

- Ubicación fija del btyecode: Siempre en Metaspace.
- Flexibilidad de ejecución: Pueden ejecutarse en la Pila de cualquier hilo.
- Sin estado: No poseen 'this', no dependen de instancias de objetos.
- Compartidos: Todos los hilos comparten la misma porción de bytecode.
- Unidad de sincronización: Bloqueo de clase (objeto Class).

### 4. Impacto en el Rendimiento

- Al inicio: La carga de clases tiene un costo; la inicialización estática ocurre una sola vez.
- En tiempo de ejecución: La invocación de métodos estáticos y de instancia tiene un costo similar.
- Memoria: Las variables estáticas permanecen en memoria y pueden contribuir a fugas de memoria si no se gestionan adecuadamente.
- Concurrencia: Los métodos estáticos requieren atención para garantizar la seguridad en hilos (thread safety).

Etiquetas: jvm carga de clases asignación de memoria heap stack

Publicado el 7-22 10:29