JavaRush /Blogue Java /Random-PT /Compilando e executando aplicativos Java nos bastidores
Павел Голов
Nível 34
Москва

Compilando e executando aplicativos Java nos bastidores

Publicado no grupo Random-PT

Contente:

  1. Introdução
  2. Compilando para bytecode
  3. Exemplo de compilação e execução de programa
  4. Executando um programa em uma máquina virtual
  5. Compilação Just-in-time (JIT)
  6. Conclusão
Compilando e executando aplicativos Java nos bastidores - 1

1. Introdução

Olá a todos! Hoje gostaria de compartilhar conhecimento sobre o que acontece nos bastidores da JVM (Java Virtual Machine) depois de executarmos um aplicativo escrito em Java. Hoje em dia, existem ambientes de desenvolvimento modernos que ajudam a evitar pensar nos aspectos internos da JVM, compilando e executando código Java, o que pode fazer com que novos desenvolvedores percam esses aspectos importantes. Ao mesmo tempo, perguntas sobre esse tema são frequentemente feitas durante as entrevistas, por isso decidi escrever um artigo.

2. Compilação para bytecode

Compilando e executando aplicativos Java nos bastidores - 2
Vamos começar com a teoria. Quando escrevemos qualquer aplicativo, criamos um arquivo com uma extensão .javae colocamos nele o código na linguagem de programação Java. Esse arquivo contendo código legível por humanos é chamado de arquivo de código-fonte . Assim que o arquivo de código-fonte estiver pronto, você precisa executá-lo! Mas no estágio contém informações compreensíveis apenas para humanos. Java é uma linguagem de programação multiplataforma. Isso significa que programas escritos em Java podem ser executados em qualquer plataforma que possua um sistema Java runtime dedicado instalado. Este sistema é denominado Java Virtual Machine (JVM). Para traduzir um programa do código-fonte em um código que a JVM possa entender, você precisa compilá-lo. O código compreendido pela JVM é denominado bytecode e contém um conjunto de instruções que a máquina virtual executará posteriormente. Para compilar o código-fonte em bytecode, existe um compilador javacincluído no JDK (Java Development Kit). Como entrada, o compilador aceita um arquivo com extensão .java, contendo o código fonte do programa, e como saída, produz um arquivo com extensão .class, contendo o bytecode necessário para que o programa seja executado pela máquina virtual. Depois que um programa for compilado em bytecode, ele poderá ser executado usando uma máquina virtual.

3. Exemplo de compilação e execução de programa

Suponha que temos um programa simples, contido em um arquivo Calculator.java, que recebe 2 argumentos numéricos de linha de comando e imprime o resultado de sua adição:
class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
Para compilar este programa em bytecode, usaremos o compilador javacna linha de comando:
javac Calculator.java
Após a compilação, recebemos como saída um arquivo com bytecode Calculator.class, que podemos executar usando a máquina java instalada em nosso computador usando o comando java na linha de comando:
java Calculator 1 2
Observe que após o nome do arquivo foram especificados 2 argumentos de linha de comando - números 1 e 2. Após a execução do programa, a linha de comando exibirá o número 3. No exemplo acima, tínhamos uma classe simples que vive por conta própria . Mas e se a classe estiver em algum pacote? Vamos simular a seguinte situação: criar diretórios src/ru/javarushe colocar nossa classe lá. Agora está assim (adicionamos o nome do pacote no início do arquivo):
package ru.javarush;

class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
Vamos compilar essa classe com o seguinte comando:
javac -d bin src/ru/javarush/Calculator.java
Neste exemplo, usamos uma opção adicional do compilador -d binque coloca os arquivos compilados em um diretório bincom uma estrutura semelhante ao diretório src, mas o diretório bindeve ser criado antecipadamente. Esta técnica é usada para evitar confundir arquivos de código-fonte com arquivos de bytecode. Antes de executar o programa compilado, vale a pena explicar o conceito classpath. Classpathé o caminho relativo ao qual a máquina virtual procurará pacotes e classes compiladas. Ou seja, desta forma informamos à máquina virtual quais diretórios no sistema de arquivos são a raiz da hierarquia de pacotes Java. Classpathpode ser especificado ao iniciar o programa usando o sinalizador -classpath. Iniciamos o programa usando o comando:
java -classpath ./bin ru.javarush.Calculator 1 2
Neste exemplo, exigimos o nome completo da classe, incluindo o nome do pacote no qual ela reside. A árvore de arquivos final fica assim:
├── src
│     └── ru
│          └── javarush
│                  └── Calculator.java
└── bin
      └── ru
           └── javarush
                   └── Calculator.class

4. Execução do programa por uma máquina virtual

Então, lançamos o programa escrito. Mas o que acontece quando um programa compilado é iniciado por uma máquina virtual? Primeiro, vamos descobrir o que significam os conceitos de compilação e interpretação de código. Compilação é a tradução de um programa escrito em uma linguagem fonte de alto nível em um programa equivalente em uma linguagem de baixo nível semelhante ao código de máquina. A interpretação é uma análise, processamento e execução imediata do programa de origem ou solicitação (comando por linha, linha por linha) do operador por instrução (em oposição à compilação, na qual o programa é traduzido sem executá-lo). A linguagem Java possui um compilador ( javac) e um interpretador, que é uma máquina virtual que converte bytecode em código de máquina linha por linha e o executa imediatamente. Assim, quando executamos um programa compilado, a máquina virtual passa a interpretá-lo, ou seja, a conversão linha por linha do bytecode em código de máquina, bem como sua execução. Infelizmente, a interpretação pura de bytecode é um processo bastante longo e torna o Java lento em comparação com seus concorrentes. Para evitar isso, foi introduzido um mecanismo para agilizar a interpretação do bytecode pela máquina virtual. Esse mecanismo é chamado de compilação Just-in-time (JITC).

5. Compilação Just-in-time (JIT)

Em termos simples, o mecanismo de compilação Just-In-Time é este: se houver partes do código no programa que são executadas muitas vezes, elas poderão ser compiladas uma vez em código de máquina para acelerar sua execução no futuro. Depois de compilar tal parte do programa em código de máquina, com cada chamada subsequente para esta parte do programa, a máquina virtual executará imediatamente o código de máquina compilado em vez de interpretá-lo, o que irá naturalmente acelerar a execução do programa. A aceleração do programa é obtida aumentando o consumo de memória (precisamos armazenar o código de máquina compilado em algum lugar!) e aumentando o tempo gasto na compilação durante a execução do programa. A compilação JIT é um mecanismo bastante complexo, então vamos exagerar. Existem 4 níveis de compilação JIT de bytecode em código de máquina. Quanto maior o nível de compilação, mais complexa ela será, mas ao mesmo tempo a execução de tal seção será mais rápida do que uma seção de nível inferior. JIT – O compilador decide qual nível de compilação definir para cada fragmento de programa com base na frequência com que esse fragmento é executado. Nos bastidores, a JVM usa 2 compiladores JIT - C1 e C2. O compilador C1 também é chamado de compilador cliente e é capaz de compilar código apenas até o 3º nível. O compilador C2 é responsável pelo quarto nível de compilação, mais complexo e mais rápido.
Compilando e executando aplicativos Java nos bastidores - 3
Do exposto, podemos concluir que para aplicações clientes simples é mais lucrativo utilizar o compilador C1, pois neste caso é importante para nós a rapidez com que a aplicação inicia. Aplicativos de longa duração do lado do servidor podem demorar mais para iniciar, mas no futuro eles deverão funcionar e executar sua função rapidamente - aqui o compilador C2 é adequado para nós. Ao executar um programa Java na versão x32 da JVM, podemos especificar manualmente qual modo queremos usar usando os sinalizadores -cliente -server. Quando esse sinalizador for especificado, -clienta JVM não executará otimizações complexas de bytecode, o que acelerará o tempo de inicialização do aplicativo e reduzirá a quantidade de memória consumida. Ao especificar o sinalizador, -servero aplicativo demorará mais para iniciar devido a otimizações complexas de bytecode e usará mais memória para armazenar código de máquina, mas o programa será executado mais rapidamente no futuro. Na versão x64 da JVM, o sinalizador -clienté ignorado e a configuração do servidor de aplicativos é usada por padrão.

6. Conclusão

Isso conclui minha breve visão geral de como funciona a compilação e execução de um aplicativo Java. Pontos principais:
  1. O compilador javac converte o código-fonte de um programa em bytecode que pode ser executado em qualquer plataforma na qual a máquina virtual Java esteja instalada;
  2. Após a compilação, a JVM interpreta o bytecode resultante;
  3. Para acelerar os aplicativos Java, a JVM usa um mecanismo de compilação Just-In-Time que converte as seções de um programa executadas com mais frequência em código de máquina e as armazena na memória.
Espero que este artigo tenha ajudado você a obter uma compreensão mais profunda de como funciona nossa linguagem de programação favorita. Obrigado pela leitura, críticas são bem-vindas!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION