Análisis en profundidad del código de bytes de Java

Tabla de contenido

1. Código fuente de demostración

2. código de bytes

3.archivo de clase descompilar archivo java

4. Estructura del código de bytes

4.1 Número mágico

Editar número de versión 4.2

4.3 Reserva constante

4.3.1 Contador de pool constante

4.3.2 Mesa de billar constante

​Editar 4.3.3 Tipos y estructuras constantes

4.3.4 Interpretación de constantes

5. Identificación de acceso

6. Índice de clase, índice de clase principal, índice de interfaz

7. Colección de mesas de campo

 8. Colección de tablas de métodos

9. Recogida de fichas de propiedades


1. Código fuente de demostración

Primero, escriba un código fuente Java simple:

paquete com.april.test;

Demostración de clase pública {     private int num = 1;

    public int add() {         num = num + 2;         devolver si;     } }




 


2. código de bytes


Para ejecutar una parte del código fuente de Java, primero debe convertir el código fuente en un archivo de clase. Un archivo de clase es un archivo de código de bytes binario compilado por el compilador para que la máquina virtual lo interprete y ejecute. Puede compilar el código fuente en un archivo de clase a través de la herramienta IDE o la línea de comando. Aquí usamos la línea de comando para operar, ejecute el siguiente comando:

javac Demo.java

generará un archivo Demo.class.
Abramos este archivo Demo.class y echemos un vistazo. Notepad ++ se usa aquí, y se necesita instalar un complemento HEX-Editor.
El archivo de formato de bytecode es hexadecimal, abierto en Excel, una celda es un byte


3.archivo de clase descompilar archivo java


Antes de analizar el archivo de clase, echemos un vistazo al resultado de descompilar Demo.class de nuevo a Demo.java, como se muestra en la siguiente figura:

 


Se puede ver que el código fuente compilado tiene un constructor vacío y esta palabra clave más que el código escrito, ¿por qué? Deja esta pregunta primero, después de leer este análisis, creo que sabrás la respuesta.

4. Estructura del código de bytes
 

En el archivo de código de bytes anterior, podemos ver que hay un montón de bytes hexadecimales dentro. Entonces, ¿cómo interpretarlo? No te preocupes, primero veamos una tabla:


Esta es una tabla de estructura general del código de bytes de Java, podemos interpretarla una por una de acuerdo con el orden anterior.
Antes que nada, expliquemos: el archivo de clase tiene solo dos tipos de datos: número sin signo y tabla. Como se muestra en la siguiente tabla:

 

Las estructuras de datos de las diversas tablas específicas anteriores se describirán en detalle más adelante, por lo que no las enumeraré aquí por el momento.
Ok, ahora comenzamos a interpretar el hexadecimal de ese grupo.

4.1 Número mágico


Como se puede ver en el diagrama de estructura general anterior, los primeros 4 bytes representan el número mágico


Número mágico

El número entero sin signo de 4 bytes al comienzo de cada archivo de Clase se denomina Número Mágico.
Su única función es determinar si el archivo es un archivo de Clase válido y legal que puede ser aceptado por la máquina virtual. Es decir: el número mágico es el identificador del archivo Clase.
El valor mágico se fija en 0xCAFEBABE. no cambiará.
Si un archivo de clase no comienza con 0xCAFEBABE, la máquina virtual arrojará directamente el siguiente error al realizar la verificación del archivo:

El uso de números mágicos en lugar de extensiones para la identificación se basa principalmente en consideraciones de seguridad, ya que las extensiones de archivo pueden cambiarse de forma arbitraria.
Los archivos en formato png también tienen el mismo número mágico

 


El formato mp3 también tiene el mismo número mágico


4.2 Número de versión


Los 4 bytes que siguen al número mágico almacenan el número de versión del archivo de clase. También 4 bytes. El significado representado por los bytes 5 y 6 es el número de versión secundaria compilada versión_menor, y los bytes 7 y 8 son el número de versión principal compilada versión_principal.

 Juntos forman el número de versión de formato del archivo de clase. Por ejemplo, si el número de versión principal de un archivo de clase es M y el número de versión secundaria es m, entonces se determina que el número de versión de formato de este archivo de clase es Mm.


Eche un vistazo a los valores en nuestro código de bytes de demostración:


Los primeros dos bytes son 0x0000, lo que significa que su valor es 0,
los últimos dos bytes son 0x0034, lo que significa que su valor es 52.
Entonces, el código anterior se compila con la versión 52.0, que es jdk1.8.0.
La relación correspondiente entre el número de versión y el compilador de Java es la siguiente


El número de versión de Java comienza en 45, y el número de versión principal de cada versión principal de JDK lanzada después de JDK 1.1 aumenta en 1

Las versiones correspondientes a los archivos Class compilados por diferentes versiones del compilador Java son diferentes. Actualmente, una máquina virtual Java de versión alta puede ejecutar archivos de clase generados por un compilador de versión baja, pero una máquina virtual Java de versión baja no puede ejecutar archivos de clase generados por un compilador de versión alta. De lo contrario, la JVM generará una excepción java.lang.UnsupportedClassVersionError.

En aplicaciones reales, este problema puede ocurrir debido a la diferencia entre el entorno de desarrollo y el entorno de producción. Por lo tanto, debemos prestar especial atención a si la versión de JDK compilada para el desarrollo es coherente con la versión de JDK en el entorno de producción durante el desarrollo.

Cuando la versión JDK de la máquina virtual es 1.k (k>=2), el número de versión del formato de archivo de clase correspondiente oscila entre 45.0 y 44+k0 (ambos extremos están incluidos).

4.3 Reserva constante


El grupo de constantes es una de las áreas más ricas en contenido del archivo Class. El grupo de constantes también juega un papel vital en la resolución de campos y métodos en el archivo Class.
Con el desarrollo continuo de la máquina virtual Java, el contenido del grupo constante también es cada vez más rico. Se puede decir que el conjunto de constantes es la piedra angular de todo el archivo Class.
Después del número de versión, le sigue el número de agrupaciones constantes y varias entradas de agrupaciones constantes.

El número de constantes en el grupo de constantes no es fijo, por lo que se debe colocar un número sin signo de tipo u2 en la entrada del grupo de constantes, que representa el valor de conteo de capacidad del grupo constante (constant_pool_count). A diferencia del hábito del lenguaje en Java, este conteo de capacidad comienza desde 1 en lugar de 0.


Como se puede ver en la tabla anterior, el archivo Class usa un contador de capacidad previa (constant_pool_count) más varios elementos de datos consecutivos (constant_pool) para describir el contenido del conjunto constante. Llamamos a esta serie de datos continuos de conjuntos constantes el conjunto de conjuntos constantes.

La entrada del grupo de constantes se utiliza para almacenar varios literales y referencias de símbolos generados durante la compilación. Esta parte del contenido se almacenará en el grupo de constantes de tiempo de ejecución en el área de métodos después de cargar la clase.


4.3.1 Contador de pool constante


El siguiente es el grupo constante. Dado que el número de agrupaciones constantes no es fijo y la duración es breve, es necesario colocar dos bytes para representar el valor de recuento de la capacidad de la agrupación constante. El valor de Demo es:


Su valor es 0x0016, que es 22. Tenga en cuenta que esto es en realidad solo 21 constantes. El índice está en el rango 1-21. ¿por qué?

Por lo general, comenzamos desde 0 cuando escribimos código, pero el grupo de constantes aquí comienza desde 1, porque vacía la constante 0. Esto es para satisfacer la necesidad de que algunos datos apunten al valor de índice del conjunto de constantes para expresar el significado de "no hacer referencia a ningún elemento de conjunto de constantes" en casos específicos. En este caso, el valor de índice 0 se puede usar para indicar .

4.3.2 Mesa de billar constante


constant_pool es una estructura de tabla, con 1 ~ constant_pool_count - 1 como índice. Indica cuántos elementos constantes siguen.

El grupo de constantes almacena principalmente dos tipos de constantes: referencias literales y simbólicas

Contiene todas las constantes de cadena, nombres de clase o interfaz, nombres de campo y otras constantes a las que se hace referencia en la estructura del archivo de clase y sus subestructuras. Cada artículo en el grupo constante tiene las mismas características. El primer byte se utiliza como etiqueta de tipo para determinar el formato del elemento.Este byte se denomina byte de etiqueta (byte de marca, byte de etiqueta).
Referencias literales y simbólicas
Antes de interpretar estas constantes, necesitamos aclarar algunos conceptos.
El grupo de constantes almacena principalmente dos tipos de constantes: literales y referencias simbólicas. Como se muestra en la siguiente tabla:


El nombre completamente calificado
com/april/test/Demo es el nombre completamente calificado de la clase, simplemente reemplace el "." del nombre del paquete con "/". Para evitar confusiones entre varios nombres completos consecutivos, use A " ;" generalmente se agrega al final para indicar el final del nombre completo. No hay punto y coma después del tipo de datos básico y hay un punto y coma después del nombre completo del tipo de datos de referencia.

 Nombre simple
El nombre simple se refiere al método o nombre de campo sin decoración de tipo y parámetro.Los nombres simples del método add() y el campo num de la clase en el ejemplo anterior son add y num respectivamente.

Descriptor
La función del descriptor es describir el tipo de datos del campo, la lista de parámetros del método (incluyendo cantidad, tipo y orden) y el valor de retorno. De acuerdo con las reglas del descriptor, los tipos de datos básicos (byte, char, double, float, int, long, short, boolean) y el tipo void que representa ningún valor de retorno están representados por un carácter en mayúscula, mientras que el tipo de objeto está representado por el carácter L más objeto El nombre completo para indicar, consulte la siguiente tabla para obtener más información:



Para el tipo de matriz, cada dimensión se describirá con un carácter [ inicial, como una matriz bidimensional definida como tipo java.lang.String[][], se registrará como: [[Ljava/lang/String; ,, una matriz de enteros int[] se registra como [I.

 Cuando se usa un descriptor para describir un método, primero se describe en el orden de la lista de parámetros y luego en el valor de retorno. La lista de parámetros se coloca entre paréntesis "( )" de acuerdo con el orden estricto de los parámetros. Por ejemplo, el descriptor del método java.lang.String toString() es ( ) LJava/lang/String; y el descriptor del método int abc(int[] x, int y) es ([II) I.

La matriz de objetos creada como se muestra en la figura es [L+ nombre completo + ";"


4.3.3 Tipos y estructuras constantes


Cada artículo en el grupo constante es una tabla con 14 tipos de artículos, como se muestra en la siguiente tabla:


Los 14 tipos tienen estructuras diferentes, como se muestra en la siguiente tabla:

 


De acuerdo con la descripción de cada tipo en la figura anterior, también podemos saber para qué se utiliza cada tipo en el conjunto de constantes (principalmente literales, referencias de símbolos). Por ejemplo:
CONSTANT_Integer_info se usa para describir la información literal en el conjunto de constantes, y es solo información literal entera.
Los tipos de elementos constantes marcados 15, 16 y 18 se utilizan para admitir llamadas de lenguaje dinámico (solo se agregaron en jdk1.7).

Detalles:

La estructura CONSTANT_Class_info se usa para representar una clase o interfaz

Las estructuras CONSTAT_Fieldref_info, CONSTAHT_Methodref_infoF y lCONSTANIT_InterfaceMethodref_info representan campos, cuadrados y convenciones

La estructura CONSTANT_String_info se utiliza para representar un objeto constante de tipo String

CONSTANT_Integer_info y CONSTANT_Float_info representan constantes numéricas de 4 bytes (int y float)

Las estructuras CONSTANT_Long_info y CONSTAT_Double_info representan constantes numéricas de 8 caracteres (largo y doble)

En la tabla de pool de constantes del archivo de clase, todas las constantes de un byte que se toman prestadas ocupan el espacio de dos miembros de la tabla (elementos). Si una estructura CONSTAHT_Long_info y CNSTAHT_Double_info tiene un bit de índice n en el grupo constante, entonces un bit de índice disponible n+2 en el grupo constante, en este momento el elemento con índice n+1 en la longitud del grupo constante sigue siendo válido pero debe ser considerado no disponible.
La estructura CONSTANT_NameAndType_info se utiliza para representar un campo o método, pero a diferencia de las tres estructuras anteriores, la estructura CONSTANT_NameAndType_info no indica la clase o interfaz a la que pertenece el campo o método.

CONSTANT_Utf8_info se utiliza para representar el valor de una constante de carácter

La estructura CONSTANT_MethodHandle_info se utiliza para representar el identificador del método

La estructura CONSTANT_MethodType_info representa el tipo de método

La estructura CONSTANT_InvokeDynamic_info representa el método de arranque (método de arranque) utilizado por la instrucción de invocación dinámica, el nombre de la invocación dinámica (nombre de la invocación dinámica), los parámetros y el tipo de devolución utilizados por el método de arranque, y se puede pasar una serie de argumentos estáticos (argumento estático). al método de arranque) constantes.

4.3.4 Interpretación de constantes


Dividimos de acuerdo con la tabla de estructura de tipo anterior y determinamos los siguientes dígitos de acuerdo con el primer byte de cada constante. Si es una cadena, entonces el segundo y tercer byte también determinan la cantidad de caracteres y finalmente obtienen 21 constantes. Cada marca roja es un elemento.


El primer elemento
es la primera constante. Veamos cuál es su bandera. El primer valor es 0x0a, que es 10. Consulte la tabla anterior para ver que el tipo de elemento correspondiente es CONSTANT_Methodref_info, que es la referencia simbólica del método en la clase. . . Su estructura es:


Es decir, los siguientes 4 bytes son su contenido, que son dos elementos de índice:


El valor de los primeros dos dígitos es 0x0004, que es 4, que apunta al índice del cuarto elemento en el grupo constante;

 

Elemento 4
El valor del primer byte del elemento 4 es 0x07 En la tabla anterior, podemos ver que el tipo de elemento correspondiente es CONSTANT_Class_info.


Los últimos dos dígitos del elemento 21
son 0x0015, que es 21, que apunta al índice del elemento 21 en el conjunto de constantes. El primer dígito del elemento 21 es 0x01, lo que muestra que es una cadena.

 Póngalo en la herramienta y podrá ver que la longitud de caracteres correspondiente al elemento 21 es 16, que se traduce como java/lang/object, que corresponde al índice del nombre completo al que apunta el elemento 4.


Artículo 18
Los dos últimos dígitos del primer artículo son 0x0012

 

El valor de los dos últimos bits es 0x0012, que es 18, que apunta al índice del elemento 18 del conjunto de constantes.


El primer byte del elemento 18 es 0x0c, que es 12. Hay dos campos, uno que describe el nombre del método y otro tipo de valor de retorno seckill y lista de parámetros formales. 

 El primer byte después de eso es 0x0007

Ítem ​​7

El primer byte del séptimo elemento es 0x01, que también es una cadena. La longitud de esta cadena es de 6 bytes, lo que se traduce como

Elemento 8
El segundo byte es 0x0008, que es el elemento 8


El primer byte del elemento 8 es 0x01, lo que significa que también es una cadena de caracteres con una longitud de 3, lo que significa ()v, y el código no tiene un método de anulación de parámetros. Hasta ahora, la interpretación de la primera constante se ha completado y el método de análisis de las siguientes constantes es el mismo, por lo que no se analizará.

 El lado derecho de la siguiente figura muestra las 21 constantes decodificadas en jclasslib


Resumen 1
El punto común de estas 14 tablas (o estructuras de elementos constantes) es: el primer bit al comienzo de la tabla es un indicador (etiqueta) de tipo u1, que representa qué estructura de tabla está utilizando actualmente este elemento constante, es decir , cuyo tipo constante.
En la lista de conjuntos de constantes, el elemento constante CONSTANT_Utf8_info es un formato de codificación UTF-8 modificado para almacenar información de cadenas constantes, como cadenas literales, nombres completos de clases o interfaces, nombres simples de campos o métodos y descriptores. .
Otra característica de estas estructuras de 14 elementos constantes es que los bytes ocupados por 13 elementos constantes son fijos, y solo los bytes ocupados por CONSTANT_Utf8_info no son fijos, y su tamaño está determinado por la longitud. ¿por qué? Porque a partir del contenido almacenado en el conjunto de constantes, se puede ver que almacena literales y referencias de símbolos. Al final, estos contenidos serán una cadena. El tamaño de estas cadenas se determina al escribir un programa. Por ejemplo, si defina una clase, el nombre de la clase puede ser Tomar el largo y tomar el corto, por lo que el tamaño no se fija antes de la compilación, después de la compilación, puede conocer su longitud a través de la codificación utf-8.
Resumen 2
Constant pool: puede entenderse como el almacén de recursos en el archivo de Clase. Es el tipo de datos que está más asociado con otros proyectos en la estructura de archivos de Clase (muchos tipos de datos subsiguientes señalarán aquí), y también es el datos que ocupan el mayor espacio en el archivo Clase uno de los elementos.
¿Por qué estos contenidos deben incluirse en el grupo constante? Cuando el código Java se compila con Javac, no tiene el paso de "enlazar" como C y C++, sino que se vincula dinámicamente cuando la máquina virtual carga el archivo C1ass. Es decir, la información de diseño de memoria final de cada método y campo no se guardará en el archivo de clase, por lo que si las referencias simbólicas de estos campos y métodos no se convierten en tiempo de ejecución, no se puede obtener la dirección de entrada de memoria real y la máquina virtual no puede acceder directamente a él. Cuando la máquina virtual se está ejecutando, necesita obtener la referencia de símbolo correspondiente del grupo de constantes y luego analizarla y traducirla a una dirección de memoria específica cuando se crea o ejecuta la clase. En cuanto al contenido de la creación de clases y la vinculación dinámica, lo explicaremos en detalle durante el proceso de carga de clases de la máquina virtual.


5. Identificación de acceso


Bandera de acceso (bandera_de_acceso, bandera de acceso, bandera de acceso)

 Después del grupo constante, siga el token de acceso. La etiqueta está representada por dos bytes y se utiliza para identificar alguna clase o información de acceso a nivel de interfaz, que incluye: si la Clase es una clase o una interfaz; si se define como un tipo público; si se define como un tipo abstracto ; si es una clase, si se declara For final y así sucesivamente. Los distintos tokens de acceso son los siguientes:


Los derechos de acceso a clases suelen ser constantes que comienzan con ACC_.

Cada tipo de representación se logra estableciendo bits específicos en los 32 bits de la bandera de acceso. Por ejemplo, si es una clase final pública, la marca es ACC_PUBLIC | ACC_FINAL.

El uso de ACC_SUPER permite que la clase ubique con mayor precisión el método super.method() de la clase principal, y los compiladores modernos configurarán y usarán este indicador.


El ID de acceso del caso anterior es 0x0021, que es 0x0001+0x0020

 Nota complementaria:

Un archivo de clase con el indicador ACC_INTERFACE representa una interfaz en lugar de una clase y viceversa, representa una clase en lugar de una interfaz.
Si un archivo de clase se establece con el indicador ACC_INTERFACE, también se debe establecer el indicador ACC_ABSTRACT. Además, ya no puede configurar los indicadores ACC_FINAL, ACC_SUPER o ACC_ENUM.
Si el indicador ACC_INTERFACE no está configurado, este archivo de clase puede tener todos los demás indicadores de la tabla anterior excepto ACC_ANNOTATION. Por supuesto, a excepción de las banderas mutuamente excluyentes como ACC_FINAL y ACC_ABSTRACT. Estas dos banderas no deben establecerse al mismo tiempo.
El indicador ACC_SUPER se usa para determinar qué semántica de ejecución usa la instrucción de invocación especial en una clase o interfaz. Los compiladores que tienen como objetivo el conjunto de instrucciones de la máquina virtual Java deben establecer este indicador. Para Java SE 8 y versiones posteriores, independientemente del valor real de este indicador en el archivo de clase y del número de versión del archivo de clase, la máquina virtual de Java considera que cada archivo de clase tiene establecido el indicador ACC_SUPER.
El indicador ACC_SUPER está diseñado para ofrecer compatibilidad con versiones anteriores del código compilado por compiladores de Java anteriores. El indicador ACC_SUPER actual no tiene un significado definido en los indicadores de acceso generados por el compilador antes de JDK 1.0.2. Si se establece este indicador, la implementación de la máquina virtual Java de 0racle lo ignorará.
El indicador ACC_SYNTHETIC significa que la clase o interfaz fue generada por el compilador, no por el código fuente.
Los tipos de anotación deben tener el indicador ACC_ANNOTATION establecido. Si se establece el indicador ACC_ANNOTATION, también se debe establecer el indicador ACC_INTERFACE.
El indicador ACC_ENUM indica que la clase o su clase principal es un tipo de enumeración.


6. Índice de clase, índice de clase principal, índice de interfaz


Después de la etiqueta de acceso, se especificará la categoría de la clase, la categoría padre y la interfaz implementada en el siguiente formato:


Una clase solo puede heredar herencia única, pero puede implementar múltiples interfaces, y todas las interfaces también tienen forma de matrices.

 Estos tres elementos de datos se utilizan para determinar la relación de herencia de esta clase:

El índice de clase se utiliza para determinar el nombre totalmente calificado de esta clase.El
índice de clase padre se usa para determinar el nombre totalmente calificado de la clase padre de esta clase. Dado que el lenguaje Java no permite la herencia múltiple, solo hay un índice de clase principal. Excepto java.1ang.Object, todas las clases Java tienen clases principales. Por lo tanto, excepto java.lang.Object, todas las clases Java tienen índices de clase principal. No para e.
La colección de índice de interfaz se usa para describir qué interfaces implementa esta clase, y estas interfaces implementadas se organizarán en el índice de interfaz de izquierda a derecha según el orden de las interfaces después de la instrucción implements (si la clase en sí es una interfaz, debe ser una instrucción extends) establecido.
this_class (índice de clase)
Entero sin signo de 2 bytes, que apunta al índice del conjunto de constantes. Proporciona el nombre completo de la clase, como com/atguigu/java1/Demo. El valor de this_class debe ser un índice válido en una entrada en la tabla de agrupación de constantes. El miembro del conjunto de constantes en este índice debe ser una estructura de tipo CONSTANT_Class_info, que representa la clase o interfaz definida por este archivo de clase.

super_class (índice de clase principal)
Entero sin signo de 2 bytes, que apunta al índice del conjunto de constantes. Proporciona el nombre completo de la clase principal de la clase actual. Si no heredamos ninguna clase, hereda la clase java/lang/object por defecto. Al mismo tiempo, dado que Java no admite la herencia múltiple, solo hay una clase principal.

La clase principal a la que apunta super_class no puede ser final.

interfaces
apunta a la colección de índices de pool constante, que proporciona una referencia simbólica a todas las interfaces implementadas

Dado que una clase puede implementar múltiples interfaces, es necesario guardar los índices de múltiples interfaces en forma de matriz, lo que indica que cada índice de la interfaz también es una CONSTANT_Class que apunta al conjunto de constantes (por supuesto, debe ser una interfaz aquí, no una clase).

Ⅰ.interfaces_count (contador de interfaces)
El valor del elemento interfaces_count indica el número de superinterfaces directos de la clase o interfaz actual.

Ⅱ.interfaces[] (colección de índice de interfaz)
El valor de cada miembro en interfaces[] debe ser un valor de índice válido para un elemento en la tabla de pool constante, y su longitud es interfaces_count. Cada miembro interfaces[i] debe ser una estructura CONSTANT_Class_info, donde 0 <= i < interfaces_count. En interfaces[], el orden de las interfaces representadas por cada miembro es el mismo que el dado en el código fuente correspondiente (de izquierda a derecha), es decir, interfaces[0] corresponde a la interfaz más a la izquierda en el código fuente.


Todos son índices que apuntan al grupo de constantes. Por ejemplo, para el índice de primera clase 0x0003, vaya al tercer elemento 0x070014 del grupo de constantes. 0x0014 es 20 y luego busque el elemento número 20. La traducción es el nombre completo. de demostración

 


7. Colección de mesas de campo


Los campos
se utilizan para describir variables declaradas en interfaces o clases. Los campos incluyen variables de nivel de clase y variables de nivel de instancia, pero no incluyen variables locales declaradas dentro de métodos y bloques de código.
El nombre del campo y el tipo de datos con el que se define el campo no se pueden fijar y solo se pueden describir haciendo referencia a las constantes en el conjunto de constantes.
Apunta a la colección de índices de pool constantes, que describen la información completa de cada campo. Por ejemplo, el identificador del campo, el modificador de acceso (público, privado o protegido), si es una variable de clase o una variable de instancia (modificador estático), si es una constante (modificador final), etc.

Precauciones:

Los campos heredados de las clases principales o las interfaces implementadas no se incluirán en la colección de tablas de campos, pero es posible que se incluyan los campos que no existen en el código Java original. Por ejemplo, para mantener el acceso a la clase externa en la clase interna, se agregará automáticamente un campo que apunta a la instancia de la clase externa.
En el lenguaje Java, los campos no se pueden sobrecargar. Independientemente de si los tipos de datos y los modificadores de los dos campos son los mismos, deben usar nombres diferentes. Pero para los códigos de bytes, si los descriptores de los dos campos son inconsistentes, entonces los campos con el mismo nombre son legales.
campos_contador
campos_contador(campos_contador)

El valor de fields_count indica el número de miembros en la tabla de campos del archivo de clase actual. Utilice dos bytes para representar.

Cada miembro de la tabla de campos es una estructura field_info, que se usa para representar todos los campos de clase o campos de instancia declarados por la clase o interfaz, excluyendo las variables declaradas dentro del método y los campos heredados de la clase principal o la interfaz principal.


mesa de campo

El contador de campo en la figura anterior es 0x0001 y solo hay un campo

Ⅰ Identificador de acceso a la tabla de campos
Sabemos que un campo puede ser modificado por varias palabras clave, tales como: modificadores de alcance (público, privado, protegido), modificadores estáticos, modificadores finales, modificadores volátiles, etc. Por lo tanto, se puede usar para marcar campos con algunas banderas como las banderas de acceso de la clase. Las banderas de acceso al campo son las siguientes:

Nombre del indicador Valor del indicador Significado
ACC_PUBLIC 0x0001 Si el campo es público
ACC_PRIVATE 0x0002 Si el campo es privado
ACC_PROTECTED 0x0004 Si el campo está protegido
ACC_STATIC 0x0008 Si el campo es estático ACC_FINAL 0x0010 Si el
campo es final ACC_VOLATILE 0x0040 Si el
campo es volátil
ACC_TRANSTENT 0x0080 si el campo es transitorio
ACC_SYNCHETIC 0x1000 Si el compilador lo genera automáticamente
ACC_ENUM 0x4000 Si el campo es enum
Ⅱ El índice de nombre de campo
0x0005 corresponde a la quinta constante,
que corresponde a la codificación utf8, que se traduce como num

Ⅲ Índice de descriptor
La función del descriptor es describir el tipo de datos del campo, la lista de parámetros del método (incluyendo cantidad, tipo y orden) y el valor devuelto. De acuerdo con las reglas del descriptor, los tipos de datos básicos (byte, char, double, float, int, long, short, boolean) y el tipo void que representa ningún valor de retorno se representan con un carácter en mayúscula, mientras que el objeto se representa con el carácter L más el nombre completo del objeto, de la siguiente manera:


Ⅳ Conjunto de tabla de atributos
El valor en la figura anterior es 0x0000, lo que significa que no, y los siguientes campos están terminados. Agregue constantes finales para tener información de inicialización


Un campo también puede tener algunos atributos para almacenar más información adicional. Como el valor de inicialización, alguna información de anotación, etc. El número de atributos se almacena en atributo_recuento y el contenido específico de los atributos se almacena en la matriz de atributos.

// Tome atributos constantes como ejemplo, la estructura es:

Descripción: Para atributos constantes, el valor de atributo_longitud siempre es 2.

 8. Colección de tablas de métodos


métodos: apunta a una colección de índice de grupo constante, que describe completamente la firma de cada método.

En el archivo de bytecode, cada elemento method_info corresponde a la información del método en una clase o interfaz. Por ejemplo, el modificador de acceso del método (público, privado o protegido), el tipo de valor de retorno del método y la información de parámetros del método.
Si el método no es abstracto o no es nativo, se reflejará en el código de bytes.
Por un lado, la tabla de métodos solo describe los métodos declarados en la clase o interfaz actual, sin incluir los métodos heredados de las clases o interfaces principales. Por otro lado, la tabla de métodos puede tener métodos agregados automáticamente por el compilador, el más típico es la información del método generada por el compilador (por ejemplo: método de inicialización de clase (interfaz) () y método de inicialización de instancia ()).
Precauciones de uso:
En el lenguaje Java, para sobrecargar un método, además de tener el mismo nombre simple que el método original, también se requiere que tenga una firma diferente al método original, y la firma es un método. de referencias de símbolos de campo de cada parámetro en el conjunto de constantes, es decir, porque el valor devuelto no se incluirá en la firma, por lo que es imposible sobrecargar un método existente en el lenguaje Java solo confiando en la diferencia en el valor devuelto . Pero en el formato de archivo de clase, el alcance de la firma de características es mayor, siempre que los descriptores no sean exactamente iguales, los dos métodos pueden coexistir. En otras palabras, si dos métodos tienen el mismo nombre y firma, pero devuelven valores diferentes, pueden coexistir legalmente en el mismo archivo de clase.

Es decir, aunque la especificación de sintaxis de Java no permite declarar varios métodos con la misma firma en una clase o interfaz, al contrario de la especificación de sintaxis de Java, el archivo de código de bytes solo permite almacenar varios métodos con la misma firma. la condición es que los valores de retorno entre estos métodos no pueden ser los mismos.


contador de métodos
method_count(contador de métodos)

El valor de method_count indica el número de miembros en la tabla de métodos del archivo de clase actual. Utilice dos bytes para representar.

Cada miembro de la tabla de métodos es una estructura method_info.

Tabla de métodos
Cada miembro de la tabla de métodos debe ser una estructura method_info, que se utiliza para representar una descripción completa de un método en la clase o interfaz actual. Si el elemento access_flags de una estructura method_info no tiene establecidos ni el indicador ACC_NATIVE ni el indicador ACC_ABSTRACT, entonces la estructura también debe contener las instrucciones de la máquina virtual Java que se utilizan para implementar este método.

La estructura method_info puede representar todos los métodos definidos en clases e interfaces, incluidos métodos de instancia, métodos de clase, métodos de inicialización de instancia y métodos de inicialización de clase o interfaz.

La estructura de la tabla de métodos es en realidad la misma que la de la tabla de campos.La estructura de la tabla de métodos es la siguiente:


Ⅰ. Bandera de acceso a la tabla de métodos
Al igual que la tabla de campos, la tabla de métodos también tiene banderas de acceso, y algunas de sus banderas son las mismas y otras son diferentes. Las banderas de acceso específicas de la tabla de métodos son las siguientes:

 Ⅱ El índice de nombres de métodos
es similar al índice de nombres de campos

Ⅲ Índice de descriptor
Similar al índice de descripción de campo

9. Recogida de fichas de propiedades


El valor del contador de
atributosAttributes_count indica el número de miembros en la tabla de atributos del archivo de clase actual. Cada elemento de la tabla de atributos es una estructura de atributo_info.

atributos de la tabla de atributos
[] (tabla de atributos)

El valor de cada elemento de la tabla de atributos debe ser una estructura de atributo_info. La estructura de la tabla de atributos es relativamente flexible. Varios atributos solo necesitan cumplir con la siguiente estructura. Cada tabla de atributos debe seguir la siguiente estructura, y cada tabla de atributos va seguida de su propia estructura de atributos.

Formato común para atributos


tipo de atributo 

Las tablas de atributos en realidad pueden tener muchos tipos, y el atributo Code que se ve arriba es solo uno de ellos. Hay 23 atributos definidos en Java8. Las siguientes propiedades están predefinidas en una máquina virtual:


O (consulte el sitio web oficial)

 

 Explicación detallada de algunos atributos.

① Atributo ConstantValue

La propiedad ConstantValue representa el valor de un campo constante. Ubicado en la tabla de atributos de la estructura field_info.


② Atributo en desuso


③ Atributo de código

 El atributo Code es para almacenar el código en el cuerpo del método. Sin embargo, no todas las tablas de métodos tienen un atributo Code. Al igual que las interfaces o los métodos abstractos, no tienen un cuerpo de método específico, por lo que no habrá ningún atributo Code. La estructura de la tabla de atributos Código es la siguiente:


Se puede ver que los primeros dos elementos de la tabla de atributos de Código son consistentes con la tabla de atributos, es decir, la tabla de atributos de Código sigue la estructura de la tabla de atributos, y los últimos son sus estructuras personalizadas.

 ④ Atributo de clases internas

Para facilitar la descripción, un formato de clase que representa una clase o interfaz se define especialmente como C. Si el conjunto de constantes de C contiene un miembro CONSTANT_Class_info y la clase o interfaz representada por este miembro no pertenece a ningún paquete, entonces la tabla de atributos de la estructura ClassFile de C debe contener el atributo InnerClasses correspondiente. El atributo InnerClasses se introdujo en JDK1.1 para admitir clases internas e interfaces internas, y se encuentra en la tabla de atributos de la estructura ClassFile.

⑤ propiedad LineNumberTable

El atributo LineNumberTable es un atributo opcional de longitud variable ubicado en la tabla de atributos de la estructura Code.

El atributo LineNumberTable se utiliza para describir la correspondencia entre los números de línea del código fuente de Java y los números de línea del código de bytes. Esta propiedad se puede usar para ubicar la línea de ejecución del código durante la depuración.

start_pc, es decir, el número de línea del código de bytes; line_number, es decir, el número de línea del código fuente de Java.
En la tabla de atributos del atributo Code, el atributo LineNumberTable puede aparecer en cualquier orden. Además, varios atributos de LineNumberTable pueden representar conjuntamente el contenido representado por un número de línea en el archivo de origen, es decir, el atributo LineNumberTable no necesita corresponder. a las líneas del archivo fuente.


⑥Propiedad LocalVariableTable

 LocalVariableTable es un atributo opcional de longitud variable ubicado en la tabla de atributos del atributo Code. Los depuradores lo utilizan para determinar información sobre variables locales durante la ejecución del método. En la tabla de atributos del atributo Code, el atributo LocalVariableTable puede aparecer en cualquier orden. Cada variable local en el atributo Code puede tener como máximo un atributo LocalVariableTable.

start pc + length indica la posición de compensación del inicio y el final del ciclo de vida de esta variable en el código de bytes (este ciclo de vida es desde el principio e hasta el final 10) index es la ranura de esta variable en la tabla de variables locales
( la ranura se puede restablecer Usar)
el nombre es el nombre de la variable
El descriptor indica la descripción del tipo de variable local

⑦ Atributo de la firma

 El atributo Signature es un atributo opcional de longitud fija ubicado en la tabla de atributos de la estructura ClassFile, field_info o method_info. En el lenguaje Java, si la firma genérica de cualquier clase, interfaz, método de inicialización o miembro contiene variables de tipo (Variables de tipo) o tipos parametrizados (Tipos parametrizados), el atributo Firma registrará información de firma genérica.

⑧ Propiedad de archivo fuente

Estructura de atributos de SourceFile


Como puede ver, su longitud siempre se fija en 8 bytes.
¿Por qué la longitud de atributo_longitud es 2? El sitio web oficial explica que debe ser 2

 ⑨ Otras propiedades

Hay más de 20 atributos predefinidos en la máquina virtual de Java, por lo que no los presentaré uno por uno aquí. A través de la introducción de los atributos anteriores, siempre que comprenda la esencia, la interpretación de otros atributos también es fácil.

Análisis del caso
Se puede ver en el código de bytes que el índice del nombre del atributo es de dos bytes 0x0009, que corresponde al código, por lo que los siguientes bytes son la estructura del atributo del código


La estructura de la tabla de atributos del código.

 

 El nombre del atributo es 0x0009, correspondiente a la
longitud del atributo de código 0x00000038, que corresponde a 54 bytes, por lo que el primer 0x00 rojo es el atributo de código. La
profundidad máxima de la pila de operandos es 0x0002, que corresponde al
espacio de almacenamiento requerido para 2 locales tablas de variables 0x0001, que corresponde a La longitud de la instrucción del
código de 1 byte es 0x0000000a, que corresponde a 10 bytes, pero solo hay 6 instrucciones según jclasslib


Analice línea por línea, haga clic en el documento oficial correspondiente a la primera instrucción, puede ver que aload_0 corresponde a 0x2a, que solo corresponde al primer byte, y así sucesivamente, la segunda instrucción corresponde a 0xb7, y en la segunda instrucción invoque especial Finalmente, el n.° 1 corresponde a un índice de grupo constante 0x0001, que analizamos anteriormente solo corresponde a java/lang/Object, por lo que el n.° 2 a continuación también tiene dos bytes que corresponden a un índice, que tiene exactamente 10 bytes de longitud.


El contador de atributos es 2 y el método tiene dos atributos, **LineNumberTable y LocalVariableTable**


Todos siguen el formato del atributo.

line_number_table_length为2

 El segundo es LocalVariableTable

La última línea es el atributo sourcefile, que también sigue el formato del atributo

Supongo que te gusta

Origin blog.csdn.net/s_nshine/article/details/132104410
Recomendado
Clasificación