java - ¿Qué es un marco de mapa de pila
jvm bytecode (1)
Recientemente he estado buscando en las especificaciones de la máquina virtual de Java (JVMS) para intentar comprender mejor qué es lo que hace que funcionen mis programas, pero he encontrado una sección que no estoy ...
La sección 4.7.4 describe el atributo StackMapTable , y en esa sección el documento detalla los marcos de mapa de pila. El problema es que es un poco prolijo y aprendo mejor con el ejemplo; no por la lectura.
Entiendo que el primer marco de mapa de pila se deriva del descriptor del método, pero no entiendo cómo (lo que supuestamente se explica here ). Además, no entiendo completamente lo que hacen los marcos de mapa de pila. Supongo que son similares a los bloques en Java, pero parece que no se pueden tener marcos de mapas de pila uno dentro del otro.
De todos modos, tengo dos preguntas específicas:
- ¿Qué hacen los marcos de mapa de pila?
- ¿Cómo se crea el primer marco de mapa de pila?
y una pregunta general:
- ¿Puede alguien proporcionar una explicación menos prolija y fácil de entender que la que se da en el JVMS?
Java requiere que todas las clases que se carguen estén verificadas, para mantener la seguridad de la zona de pruebas y garantizar que el código sea seguro para optimizar. Tenga en cuenta que esto se realiza en el nivel de bytecode, por lo que la verificación no verifica las invariantes del lenguaje Java, simplemente verifica que el bytecode tenga sentido de acuerdo con las reglas para bytecode.
Entre otras cosas, la verificación de bytecode se asegura de que las instrucciones estén bien formadas, que todos los saltos sean válidos dentro del método y que todas las instrucciones operen en valores del tipo correcto. El último es donde entra el mapa de pila.
La cosa es que el bytecode por sí mismo no contiene información explícita de tipo. Los tipos se determinan implícitamente a través del análisis de flujo de datos. Por ejemplo, una instrucción iconst crea un valor entero. Si lo almacena en la ranura 1, esa ranura ahora tiene un int. Si el flujo de control se combina con el código que almacena un flotador allí, se considera que la ranura tiene un tipo no válido, lo que significa que no puede hacer nada más con ese valor hasta que se sobrescriba.
Históricamente, el verificador de bytecode infería todos los tipos usando estas reglas de flujo de datos. Desafortunadamente, es imposible inferir todos los tipos en un solo paso lineal a través del bytecode porque un salto hacia atrás podría invalidar los tipos ya inferidos. El verificador clásico resolvió esto iterando a través del código hasta que todo dejó de cambiar, lo que posiblemente requería múltiples pases.
Sin embargo, la verificación hace que la carga de clases sea lenta en Java. Oracle decidió resolver este problema agregando un verificador nuevo y más rápido, que pueda verificar el código de bytes en una sola pasada. Para hacer esto, requirieron que todas las nuevas clases que comienzan en Java 7 (con Java 6 en un estado de transición) lleven metadatos sobre sus tipos, para que el código de bytes pueda verificarse en una sola pasada. Dado que el formato de bytecode en sí no se puede cambiar, esta información de tipo se almacena por separado en un atributo llamado StackMapTable
.
El simple hecho de almacenar el tipo para cada valor individual en cada punto del código obviamente ocuparía mucho espacio y sería un gran desperdicio. Para hacer que los metadatos sean más pequeños y más eficientes, decidieron tenerlos solo en una lista de los tipos en las posiciones que son objetivos de saltos . Si lo piensa, esta es la única vez que necesita la información adicional para realizar una verificación de un solo pase. Entre los objetivos de salto, todo el flujo de control es lineal, por lo que puede inferir los tipos en las posiciones intermedias utilizando las antiguas reglas de inferencia.
Cada posición donde se enumeran explícitamente los tipos se conoce como un marco de mapa de pila. El atributo StackMapTable
contiene una lista de marcos en orden, aunque generalmente se expresan como una diferencia del marco anterior para reducir el tamaño de los datos. Si no hay marcos en el método, lo que ocurre cuando el flujo de control nunca se une (es decir, el CFG es un árbol), el atributo StackMapTable se puede omitir por completo.
Entonces, esta es la idea básica de cómo funciona StackMapTable y por qué se agregó. La última pregunta es cómo se crea el marco inicial implícito. La respuesta, por supuesto, es que al comienzo del método, la pila de operandos está vacía y las ranuras de variables locales tienen los tipos dados por los tipos de parámetros del método, que se determinan a partir del decriptor del método.
Si está acostumbrado a Java, existen algunas pequeñas diferencias en el funcionamiento de los tipos de parámetros de método en el nivel de bytecode. En primer lugar, los métodos virtuales tienen implícito this
como primer parámetro. En segundo lugar, boolean
, byte
, char
y short
no existen en el nivel de bytecode. En su lugar, todos se implementan como intentos detrás de escena.