¿Cómo se ejecuta el código Java en la máquina para comenzar con los conceptos básicos del desarrollo de Java?

Lo que la computadora puede reconocer es el código de instrucción de la máquina, denominado código de máquina. El código de máquina es binario y las computadoras pueden reconocerlo directamente, pero es demasiado diferente del lenguaje humano, por lo que no es fácil de entender y recordar para las personas. Más tarde, nacieron varios lenguajes de alto nivel: las personas escriben programas en lenguajes de alto nivel y luego interpretan o compilan los programas en códigos de máquina.

Python, por ejemplo, es un lenguaje interpretado. No es necesario compilar el código fuente del programa Python y el programa se puede ejecutar directamente desde el código fuente. El intérprete de Python convierte el código fuente en código de bytes y luego reenvía el código de bytes compilado a la máquina virtual Python (PVM) para su ejecución.

El lenguaje C es un lenguaje compilado típico, que primero debe compilarse en código de máquina con un compilador. Por ejemplo, generalmente usamos gcc para compilar programas en lenguaje C:

$ gcc hola.c # compilar

$ ./a.out # ejecutar

¡Hola Mundo!

Entonces, ¿Java es un lenguaje interpretado o un lenguaje compilado?

"Java tiene las características tanto de un lenguaje compilado como de un lenguaje interpretado". Después de que el programador escribe el programa Java, necesita usar javac para compilarlo en un archivo de clase de código de bytes que la JVM pueda usar. Luego, la JVM carga el archivo de clase, lo interpreta y ejecuta uno por uno. Durante el proceso de ejecución, el compilador justo a tiempo compilará algunos códigos activos en código de máquina.

código fuente a código de bytes

El código fuente del lenguaje Java es un archivo con el sufijo .java. Por supuesto, muchos otros lenguajes de alto nivel también se basan en la JVM, como groovy, kotlin, etc. El código fuente es para que la gente lo vea, sea fácil de leer, comprender y mantener.

El código fuente se compila para obtener el código de bytes, que utiliza la JVM y es fácil de entender e identificar. El código de bytes tiene el sufijo .class y su formato es un conjunto de planes de JVM. Los humanos apenas pueden entender el código de bytes en comparación con el documento, pero es más difícil de entender que el código Java.

Java es diferente de Python. Python no necesita compilar archivos de código de bytes (por supuesto, Python también proporciona esta operación). La compilación es un proceso automático y, en general, no le importa su existencia. Java compilará primero el archivo de código de bytes, de modo que la JVM pueda leer directamente el archivo de código de bytes, lo que puede ahorrar tiempo de carga de módulos y mejorar la eficiencia. Al mismo tiempo, la forma del código de bytes también aumenta la dificultad de la ingeniería inversa, que puede proteger el código fuente (por supuesto, también se puede descompilar).

Los amigos que están familiarizados con JVM saben que tiene un "proceso de carga de clases", que se puede decir que es un viejo estereotipo y que los entrevistadores suelen preguntar. El proceso de carga de clases en realidad se refiere a todo el proceso de la JVM, desde leer un archivo de clase hasta preparar la clase y finalmente destruirla.

Entonces, "los archivos de clase en realidad se basan en" clases ", que son algo diferentes de los archivos java". Si declaramos varias clases en un archivo Java, encontraremos varios archivos de clases cuando se compila con Javac. Por ejemplo, declaramos un archivo One.java:

clase pública uno {

clase pública OneInner {}

clase privada OnePrivateInner {}

clase estática pública OneStaticInner {}

clase estática privada OneprivateStaticInner {}

}

clase dos{}

Después de compilar con Javac, habrá 6 archivos de clase.

➜ $ls

'Un error de análisis de KaTeX: doble superíndice en la posición 25: …clase' '̲One OneStaticInner.class' One.class Two.class

'Un error de análisis de KaTeX: doble superíndice en la posición 25: …eInner.class' '̲One OneprivateStaticInner.class' One.java

código de bytes a código de máquina

Cargar y usar código de bytes

Como se mencionó anteriormente, la JVM cargará el archivo de clase y luego la clase Java cargada se almacenará en el Área de métodos. Comience a ejecutar desde el método principal de la clase especificada como punto de entrada. Cuando realmente se ejecuta, la máquina virtual ejecuta el código en el área de método y la JVM usa el montón y la pila para almacenar datos en tiempo de ejecución.

Cada vez que se ingresa un método, la máquina virtual Java genera un marco de pila en la pila del subproceso actual para almacenar variables locales y operandos de código de bytes. El tamaño de este marco de pila se calcula de antemano.
IMG_256
Al salir de un método, ya sea un retorno normal o un retorno anormal, la máquina virtual Java "aparecerá el marco de pila actual del hilo actual" y lo descartará.

La máquina virtual Java necesita traducir el código de bytes en código de máquina para que la máquina pueda ejecutarlo. Hay dos formas de este proceso, una es interpretación y ejecución, que traduce códigos de bytes en códigos de máquina uno por uno y los ejecuta; la otra es la compilación Just-In-Time (JIT), que consiste en incluir "en un método" todos Los códigos de bytes se compilan en código de máquina y luego se ejecutan.
IMG_257
compilación en capas

¿Cómo cooperan estos dos métodos de compilación?

La máquina virtual HotSpot incluye múltiples compiladores justo a tiempo C1, C2 y Graal. Entre ellos, Graal es un compilador experimental justo a tiempo, que se puede habilitar mediante el parámetro -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler y reemplaza a C2.

C1 y C2 tienen sus propias ventajas y desventajas y son adecuados para diferentes escenarios. Antes de Java 7, sólo se podía elegir un compilador. C1 se compila rápidamente, pero la eficiencia de ejecución del código generado es promedio. A menudo se usa para programas que tienen un tiempo de ejecución corto o tienen requisitos de rendimiento de inicio. Los programas que tardan mucho en ejecutarse o requieren un rendimiento máximo se usan a menudo en el lado del servidor. De hecho, el parámetro correspondiente a C1 es cliente y el parámetro correspondiente a C2 es servidor, que también coinciden con sus escenarios de aplicación.

Java7 introduce el concepto de compilación en capas, que combina las ventajas de rendimiento de inicio de C1 y las ventajas de rendimiento máximo de C2. El código de máquina compilado por C1 y C2 es diferente. La eficiencia de ejecución del código C2 es más de un 30% mayor que la del código C1. Cuanto más rápido sea el código de máquina, más tiempo llevará compilarlo. La compilación en capas es un método de compromiso que no solo puede satisfacer algunos de los códigos menos activos que se compilarán en poco tiempo, sino que también puede satisfacer la mejor optimización de los códigos activos.

código caliente

Entonces, ¿cómo determinar el código activo?

La JVM recopilará la información de tiempo de ejecución del método, incluida principalmente la cantidad de llamadas y la cantidad de bucles. La compilación justo a tiempo se activa cuando "la suma del número de invocaciones de métodos y el número de bucles excede el umbral especificado".

->

El número de bucles puede entenderse simplemente como el número de bucles del código dentro del método, por ejemplo, hay bucles for o while dentro del método.

<-

Antes de la aparición de la compilación en capas, este umbral se especificaba mediante el parámetro -XX:CompileThreshold. Cuando se usaba C1, el valor era 1500; cuando se usaba C2, el valor era 10000.

Cuando la compilación por niveles está habilitada, la JVM utiliza otro sistema de umbral. En este sistema, el tamaño del umbral se ajusta dinámicamente. La JVM multiplica el umbral con algunos coeficientes. Este coeficiente está correlacionado positivamente con la cantidad de métodos que se compilarán actualmente y negativamente con la cantidad de subprocesos de compilación.

compilar hilo

De forma predeterminada, el número total de subprocesos de compilación se escala según el número de procesadores. La máquina virtual Java asigna estos subprocesos de compilación a C1 y C2 (al menos 1 cada uno) en una proporción de 1:2. Por ejemplo, para una máquina de cuatro núcleos, el número total de subprocesos de compilación es 3, incluidos un subproceso de compilación C1 y dos subprocesos de compilación C2.

->

Cuando hay muy pocos recursos de la máquina, puede haber 1 subproceso para cada uno.

<-

Puedes ver los hilos de compilación con arthas:
IMG_258
Arthas
puede ver que su ID es -1 y su prioridad también es -1. La prioridad del hilo que creamos nosotros mismos es 0 ~ 10, por lo que la prioridad del hilo de compilación será mayor.

Resumir

En una oración, ¿cómo se ejecuta un programa Java en una máquina? Primero, un programador Java escribe código Java, luego el código Java se compilará en un archivo de clase y varios archivos de clase se empaquetarán en un paquete jar o en un war. paquete. Luego, la JVM carga el archivo de clase y luego primero lo interpreta y ejecuta como código de bytes. Después de que el programa se ejecuta durante un período de tiempo, la JVM continuará juzgando si un método es un código activo a través del número de llamadas al método y bucles. Si es así, utilizará la compilación en capas, lo compilará en código de bytes a través del hilo de compilación, y ejecutarlo en la máquina.

Fuente del artículo: los derechos de autor de la red pertenecen al autor original.

El contenido anterior no tiene fines comerciales, si involucra problemas de propiedad intelectual, comuníquese con el editor, lo solucionaremos de inmediato.

Supongo que te gusta

Origin blog.csdn.net/xuezhangmen/article/details/132035953
Recomendado
Clasificación