Pila de máquinas virtuales JVM

Pila de máquinas virtuales

Debido al diseño multiplataforma, las instrucciones de Java están diseñadas de acuerdo con la pila. Debido a que las diferentes plataformas tienen diferentes arquitecturas de CPU, no se pueden diseñar en base a registros.
ventaja:Multiplataforma, el conjunto de instrucciones es pequeño, el compilador es fácil de implementar, la desventaja es que el rendimiento se reduce y la misma función requiere más instrucciones.
Establecer el tamaño de la pila: -Xss 1m (tamaño)
Inserte la descripción de la imagen aquí

Estructura del marco de pila en tiempo de ejecución

El marco de pila se utiliza para almacenar la tabla de variables locales, la pila de operandos, el enlace dinámico y la salida del método. Cada método se llama al proceso de ejecución, que corresponde al proceso de un marco de pila en la pila de la pila. Todas las instrucciones de código de bytes ejecutadas por el motor de ejecución son solo para el marco de pila actual (parte superior de la pila).

Tabla de variables locales

  1. La tabla de variables locales es un espacio de almacenamiento para un conjunto de valores de variables, que se utiliza para almacenar los parámetros del método y las variables locales definidas en el método.

  2. El tamaño de la tabla de variables locales se conoce en tiempo de compilación El tamaño de la tabla de variables locales en la pila se puede calcular de acuerdo con la tabla de variables locales analizada por el archivo de clase.

  3. La capacidad de la tabla de variables locales se basa en la ranura variable (ranura) como la unidad más pequeña, si es de tipo largo o doble, se requieren dos ranuras y el índice registra la primera. Qué

  4. Reglas de asignación: si se trata de un método de instancia, en este se almacena el índice 0 y el
    resto de los parámetros se asignan en orden
    según el orden de las variables y la asignación de alcance definida en el cuerpo del método

  5. Las ranuras de variables locales se pueden reutilizar

  6. Las variables de la tabla de variables locales también son importantes nodos raíz de recolección de basura. Siempre que los objetos a los que hace referencia la tabla de variables locales no se reciclarán.

Pila de operandos

  1. La pila de operandos es una pila de último en entrar, primero en salir, y la profundidad máxima se determina en tiempo de compilación. Se utiliza principalmente para guardar los resultados intermedios del proceso de cálculo, y al mismo tiempo como espacio de almacenamiento temporal para las variables en el proceso de cálculo.

  2. La pila está vacía cuando se ejecuta un método. En el proceso de ejecución, la pila se operará continuamente a través de instrucciones de código de bytes.

  3. En la mayoría de las implementaciones de máquinas virtuales, los marcos de pila adyacentes se superpondrán parcialmente. Deje que parte de la pila de operandos del marco de pila inferior se superponga con la parte superior de las variables locales

  4. Si el método llamado tiene un valor de retorno, entonces ponga el valor de retorno en la pila

Enlace dinámico

Cada marco contiene una referencia al método al que pertenece el marco en el grupo de constantes de tiempo de ejecución. Esta referencia se mantiene para admitir el enlace dinámico durante la invocación del método.Esta cita es la población de alguna información sobre este método.(Llamamos a que algunas referencias de símbolos en el grupo de constantes de tiempo de ejecución se convierten en referencias directas en tiempo de ejecución llamado enlace dinámico)
Inserte la descripción de la imagen aquí

Dirección de devolución del método

Cuando un método comienza a ejecutarse, solo hay dos formas de salir del método. El primero: el motor de ejecución encuentra una instrucción de código de bytes devuelta por cualquier método. El segundo tipo: encuentra una excepción. Independientemente del método de salida, debe volver al método original. En circunstancias normales, al salir normalmente, el valor del contador de PC del método de ajuste principal está bien. No se guardará al salir de forma anormal.
El proceso de salida del método:

  1. Pop el marco de pila actual
  2. Restaurar la tabla de variables locales y la pila de operandos del método superior
  3. Si hay un valor de retorno, empújelo a la pila de operandos
  4. Ajuste el valor del contador de PC a la instrucción que sigue a la instrucción de llamada

Alguna otra información

Llamada de método

Analizando

En la etapa de análisis de la carga de clases, algunas de las referencias de símbolos se convierten en referencias directas. La premisa de que se puede establecer este tipo de análisis es que el método tiene una versión de llamada definida antes de que el programa se ejecute realmente, y el tiempo de ejecución no se puede cambiar. En otras palabras, el destino de la llamada se determina cuando se escribe el código del programa y el compilador lo compila. La invocación de tales métodos se denomina resolución, como métodos estáticos, métodos finales, métodos privados, constructores de instancias y constructores de instancias. Debido a que estos no se cambiarán, no son métodos virtuales.

  1. invokestatic llama a un método estático
  2. invokespecial llama al método init del constructor de la instancia, método privado, método en la clase principal
  3. invokevirtual llama a métodos virtuales (los métodos finales también están dentro pero se resolverán)
  4. invokeinterface llama a un método de interfaz, y se determinará un método de implementación en tiempo de ejecución
  5. Invokedynamic ahora resuelve dinámicamente el método de invocar el calificador en tiempo de ejecución y luego ejecuta el método. (Agregado por java7, para lograr un lenguaje de tipo dinámico)

La llamada de resolución es un proceso estático, que se determina completamente durante la compilación. Y otro formulario de llamada al método principal: dispatch.

Envío

  • Despacho estático
    mira la salida del siguiente programa
public class TestClass {
    
    
    static abstract class Human{
    
    }
    static class Man extends Human{
    
    }
    static class Woman extends Human{
    
    }
    public void sayHello(Human guy){
    
    
        System.out.println("Hello guy");
    }
    public void sayHello(Man guy){
    
    
        System.out.println("Hello man");
    }
    public void sayHello(Woman guy){
    
    
        System.out.println("Hello woman");
    }

    public static void main(String[] args) {
    
    
        Human man = new Man();
        Human woman = new Woman();
        TestClass tc = new TestClass();
        tc.sayHello(man);
        tc.sayHello(woman);
    }

}
//输出:
//Hello guy
//Hello guy

Lo anterior es la sobrecarga del método (OverLoad) es decirEnvío estático de métodos

Human man = new Man();

Human llamado tipo estático de variable de código anterior , o el tipo de apariencia de llamada, mientras que la parte posterior del tipo real de variables se refiere a Man, o tipo de tiempo de ejecución.
El tipo estático de la variable en sí no se cambiará y el tipo estático final se conoce en tiempo de compilación, y el cambio de tipo real se determina en tiempo de ejecución.
En el momento de la llamada, hemos confirmado que estamos usando el objeto tc, cuya versión sobrecargada a usar depende de la cantidad de parámetros pasados ​​y del tipo de datos. Debido a que el compilador pasa el tipo estático del parámetro en lugar del tipo de tiempo de ejecución cuando se sobrecarga, la salida es Hola, chico.

  • Despacho dinámico
    Mira el resultado del siguiente programa
public class TestClass {
    
    
    static abstract class Human{
    
    
        protected abstract void sayHello();
    }
    static class Man extends Human{
    
    
        @Override
        protected void sayHello() {
    
    
            System.out.println("Hello man");
        }
    }
    static class Woman extends Human{
    
    
        @Override
        protected void sayHello() {
    
    
            System.out.println("Hello woman");
        }
    }


    public static void main(String[] args) {
    
    
        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();
        woman.sayHello();
    }
}
//输出:
//Hello man
//Hello woman

Obviamente, cualquiera que conozca Java sabe que se trata de una reescritura de Java. Entonces, ¿cómo determina la máquina virtual Java a qué método llamar?
El proceso de llamar al método de la máquina virtual Java es el proceso de envío dinámico.
Obviamente, aquí no se determinará en función del tipo estático de la variable, porque ambos son Humanos pero tienen comportamientos diferentes. Verificamos el código de bytes a través de javap y encontramos que los parámetros de la instrucción invokevirtual en los dos lugares son los mismos. La descripción es principalmente que la implementación específica en la instrucción invokevirtual se asigna dinámicamente. De acuerdo con la "Especificación de la máquina virtual de Java", el proceso de análisis de la instrucción virtual invoke se divide principalmente en los siguientes pasos.

  1. Encuentre el tipo real del objeto al que apunta el primer elemento en la parte superior de la pila de operandos y márquelo como O.
  2. Si un método que coincide con el descriptor y el nombre simple en la constante se encuentra en O, entonces la verificación del permiso de acceso se realiza durante un tiempo prolongado. Si se pasa, se devuelve la referencia directa de este método y se informa de un error si falla.
  3. De lo contrario, de acuerdo con la relación de herencia, el segundo paso de la clase padre se realiza a su vez (para mejorar el rendimiento, la JVM usa una tabla de método virtual en el área de método de la clase para lograr esto, y comienza la inicialización en la etapa de enlace.)
  4. Si no puede encontrarlo, informe un error.
    Nota: Solo hay métodos virtuales en Java, y los campos no son virtuales, lo que significa que los campos nunca participarán en polimorfismo. Cuando una subclase declara un campo con el mismo nombre que la clase principal, aunque ambos campos existirán en la memoria de la subclase, el campo de la subclase enmascarará el campo con el mismo nombre que la clase principal.

Eche un vistazo a las siguientes "preguntas negativas para la entrevista"

public class TestClass {
    
    

    static  class Father{
    
    
        public int money = 1;

        public Father() {
    
    
            this.money = 2;
            showMoney();
        }

        protected  void showMoney(){
    
    
            System.out.println("I am Father , I hava $"+money);
        }
    }

    static  class Son extends Father{
    
    
        public int money = 3;

        public Son() {
    
    
            this.money = 4;
            showMoney();
        }

        protected  void showMoney(){
    
    
            System.out.println("I am Son , I hava $"+money);
        }
    }
    public static void main(String[] args) {
    
    
        Father guy = new Son();
        System.out.println("This guy has $"+guy.money);
    }

}
//输出
/*
I am Son , I hava $0
I am Son , I hava $4
This guy has $2
*/

Explicación:
En primer lugar, porque el proceso de creación de Son llamará primero al método constructor de la clase principal para inicializar la información de campo de la clase principal. En este momento, ejecutará el método constructor de la clase principal, establecerá el dinero de la clase principal en 2 y llamará a showMoney (). En este momento, se llama al método virtual, por lo que se llama a la salida de Son en lugar de la de Father: I am Son, tengo $ 0 (debido a que el constructor de son aún no se ha ejecutado, es 0). Después de ejecutar la salida del método constructor de Son: Soy Son, tengo $ 4. La última llamada es porque el campo no admite polimorfismo, por lo que la llamada a guy.money se basa en el tipo estático de la variable para encontrar dinero. Salida: Este tipo tiene $ 2

Supongo que te gusta

Origin blog.csdn.net/null_zhouximin/article/details/112628519
Recomendado
Clasificación