Internos da JVM, Parte 2 - Estrutura do Arquivo de Classes

Olá pessoal! Uma tradução do artigo foi preparada especificamente para os alunos do curso Java Developer .




Continuamos a falar sobre como a Java Virtual Machine funciona internamente. No artigo anterior ( original em inglês ), examinamos o subsistema de carregamento de classes. Neste artigo, falaremos sobre a estrutura dos arquivos de classe.

Como já sabemos, todo o código-fonte escrito na linguagem de programação Java é compilado primeiro no bytecode usando o compilador javac , que faz parte do Java Development Kit. O bytecode é armazenado em um arquivo binário em um arquivo de classe especial. Então esses arquivos de classe são dinamicamente (se necessário) carregados na memória pelo carregador de classes (ClassLoader).


Figura - Compilação do código-fonte Java

Cada arquivo com uma .java compilado em pelo menos um arquivo .class . Para cada classe, interface e módulo definido no código-fonte, um arquivo .class é criado. Isso também se aplica a interfaces e classes aninhadas.

Nota - para simplificar, os arquivos com a extensão .class serão chamados de "arquivos de classe".

Vamos escrever um programa simples.

 public class ClassOne{ public static void main(String[] args){ System.out.println("Hello world"); } static class StaticNestedClass{ } } class ClassTwo{ } interface InterfaceOne{ } 

A execução do javac para este arquivo resultará nos seguintes arquivos.

 ClassOne$StaticNestedClass.class ClassOne.class ClassTwo.class InterfaceOne.class 

Como você pode ver, um arquivo de classe separado é criado para cada classe e interface.

O que há dentro do arquivo de classe?


O arquivo de classe está no formato binário. As informações são geralmente escritas sem recuo entre informações consecutivas, tudo está alinhado com os limites de bytes. Todos os valores de 16 e 32 bits são gravados usando dois ou quatro bytes consecutivos de 8 bits.

O arquivo de classe contém as seguintes informações.

Número mágico, assinatura . Os primeiros quatro bytes de cada arquivo de classe são sempre 0xCAFEBABE . Esses quatro bytes identificam o arquivo de classe Java.

Versão do arquivo. Os próximos quatro bytes contêm as versões principais e secundárias do arquivo. Juntos, esses números determinam a versão do formato do arquivo de classe. Se o arquivo de classe tiver uma versão principal principal de M e um m menor, designamos essa versão como Mm

Cada JVM possui limitações nas versões suportadas dos arquivos de classe. Por exemplo, o Java 11 suporta versões principais de 45 a 55, Java 12 - de 45 a 56.

Poça de constantes. Uma tabela de estruturas representando constantes de seqüência de caracteres, nomes de classes, interfaces, campos, métodos e outras constantes que estão na estrutura ClassFile e suas subestruturas. Cada elemento do conjunto constante começa com uma tag de byte único que define o tipo de constante. Dependendo do tipo de constante, os seguintes bytes podem ser um valor constante imediato ou uma referência a outro elemento no pool.

Sinalizadores de acesso. Uma lista de sinalizadores que indicam a classe é uma interface, pública ou privada, a classe final ou não. Vários sinalizadores como ACC_PUBLIC , ACC_FINAL , ACC_INTERFACE , ACC_ENUM etc. são descritos na Especificação da Java Virtual Machine.

Essa aula. Link para a entrada no pool constante.

Super classe. Link para a entrada no pool constante.

Interfaces O número de interfaces implementadas pela classe.

O número de campos. O número de campos na classe ou interface.

Campos. Após o número de campos, é apresentada uma tabela de estruturas de comprimento variável. Um para cada campo com uma descrição do tipo e nome do campo (com referência ao conjunto de constantes).

Número de métodos. O número de métodos na classe ou interface. Esse número inclui apenas métodos explicitamente definidos na classe, sem métodos herdados das superclasses.

Métodos A seguir, os próprios métodos. Para cada método, estão contidas as seguintes informações: o descritor do método (tipo de retorno e lista de argumentos), o número de palavras necessárias para as variáveis ​​locais do método, o número máximo de palavras de pilha necessárias para a pilha de operandos do método, a tabela de exceção capturada pelo método, bytecodes do método e a tabela números de linha.

O número de atributos. O número de atributos nesta classe, interface ou módulo.

Atributos O número de atributos é seguido por tabelas ou estruturas de comprimento variável que descrevem cada atributo. Por exemplo, sempre há um atributo "SourceFile". Ele contém o nome do arquivo de origem do qual o arquivo de classe foi compilado.

Embora o arquivo de classe não seja diretamente legível por humanos, existe uma ferramenta no JDK chamada javap que exibe seu conteúdo em um formato conveniente.

Vamos escrever um programa Java simples, como mostrado abaixo.

 package bytecode; import java.io.Serializable; public class HelloWorld implements Serializable, Cloneable { public static void main(String[] args) { System.out.println("Hello World"); } } 

Vamos compilar este programa com o javac , que criará o arquivo HelloWorld.class e use o javap para visualizar o arquivo HelloWorld.class . A execução do javap com a opção -v (verbose) para HelloWorld.class fornece o seguinte resultado:

 Classfile /Users/apersiankite/Documents/code_practice/java_practice/target/classes/bytecode/HelloWorld.class Last modified 02-Jul-2019; size 606 bytes MD5 checksum 6442d93b955c2e249619a1bade6d5b98 Compiled from "HelloWorld.java" public class bytecode.HelloWorld implements java.io.Serializable,java.lang.Cloneable minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // bytecode/HelloWorld super_class: #6 // java/lang/Object interfaces: 2, fields: 0, methods: 2, attributes: 1 Constant pool: #1 = Methodref #6.#22 // java/lang/Object."<init>":()V #2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #25 // Hello World #4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #28 // bytecode/HelloWorld #6 = Class #29 // java/lang/Object #7 = Class #30 // java/io/Serializable #8 = Class #31 // java/lang/Cloneable #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 LineNumberTable #13 = Utf8 LocalVariableTable #14 = Utf8 this #15 = Utf8 Lbytecode/HelloWorld; #16 = Utf8 main #17 = Utf8 ([Ljava/lang/String;)V #18 = Utf8 args #19 = Utf8 [Ljava/lang/String; #20 = Utf8 SourceFile #21 = Utf8 HelloWorld.java #22 = NameAndType #9:#10 // "<init>":()V #23 = Class #32 // java/lang/System #24 = NameAndType #33:#34 // out:Ljava/io/PrintStream; #25 = Utf8 Hello World #26 = Class #35 // java/io/PrintStream #27 = NameAndType #36:#37 // println:(Ljava/lang/String;)V #28 = Utf8 bytecode/HelloWorld #29 = Utf8 java/lang/Object #30 = Utf8 java/io/Serializable #31 = Utf8 java/lang/Cloneable #32 = Utf8 java/lang/System #33 = Utf8 out #34 = Utf8 Ljava/io/PrintStream; #35 = Utf8 java/io/PrintStream #36 = Utf8 println #37 = Utf8 (Ljava/lang/String;)V { public bytecode.HelloWorld(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 4: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lbytecode/HelloWorld; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 7: 0 line 8: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "HelloWorld.java" 

Aqui você pode ver que a classe é pública e possui 37 entradas no pool constante. Há um atributo (SourceFile abaixo), a classe implementa duas interfaces (Serializable, Cloneable), não possui campos e existem dois métodos.

Você deve ter notado que existe apenas um método principal estático no código-fonte, mas o arquivo de classe diz que existem dois métodos. Lembre-se do construtor padrão - este é um construtor sem argumento adicionado pelo compilador javac , cujo bytecode também é visível na saída. Construtores são considerados como métodos.

Você pode ler mais sobre o javap aqui .

Dica : você também pode usar o javap para ver como as lambdas diferem das classes internas anônimas.

Source: https://habr.com/ru/post/pt478584/


All Articles