Serie de entrevistas dos: Preguntas especiales seleccionadas de JVM para análisis detallado de entrevistas de Big Data con respuestas

La cuenta oficial (Five Minutes to Learn Big Data) ha lanzado una serie de entrevistas de Big Data, una pequeña entrevista de cinco minutos . Esta serie de artículos estudiará en profundidad las preguntas reales de las entrevistas de las principales fábricas y ampliará los puntos de conocimiento relevantes basados ​​en preguntas de la entrevista para ayudar a todos ¡Sea capaz de unirse con éxito a una gran fábrica!

Los artículos de la serie de entrevistas de Big Data se dividen en dos tipos: tipo mixto (es decir, habrá puntos de conocimiento de múltiples marcos en un artículo: integración); tipo especial (un artículo realiza un análisis en profundidad de un marco determinado: ejercicio especial) .

Este artículo es el segundo de una serie (especial JVM)

La primera pregunta: relacionada con la memoria JVM (Baidu)

Pregunta: ¿Entiende el modelo de memoria JVM? Hablemos de ello

responder:

Debido a que este contenido es demasiado, es posible que muchos amigos no recuerden tanto, por lo que las respuestas a continuación se dividen en respuestas cortas y respuestas refinadas .

La memoria en tiempo de ejecución de JVM se divide en cinco partes: contador de programa, pila de máquina virtual Java, pila de método local, montón y área de método :

Nota: El ajuste de JVM es principalmente para optimizar el área de método Heap Heap y Method Area

  1. Contador de programa (hilo privado):

Respuesta corta : cada hilo tiene una calculadora de programa, que es un puntero al código de bytes del método (el siguiente código de instrucción que se ejecutará) en el área de método. El motor de ejecución lee la siguiente instrucción, que es muy pequeña. ser ignorado.

Buena respuesta : ocupa un pequeño espacio de memoria, que puede verse como un indicador de número de línea del código de bytes ejecutado por el hilo actual. En el modelo conceptual de la máquina virtual, cuando el intérprete de código de bytes funciona, selecciona la siguiente instrucción de código de bytes que se ejecutará cambiando el valor de este contador. Se requieren funciones básicas como bifurcación, bucle, salto, manejo de excepciones y recuperación de subprocesos. Confíe en este contador para completar.

Dado que el subproceso múltiple de JVM se realiza mediante la conmutación de subprocesos y la asignación del tiempo de ejecución del procesador a su vez, en cualquier momento determinado, un procesador solo ejecutará instrucciones en un subproceso. Por lo tanto, en el futuro, después de que se cambie el subproceso, se puede restaurar a la posición de ejecución correcta. Cada subproceso debe tener un contador de programa independiente. Los contadores entre los subprocesos no se afectan entre sí y se almacenan de forma independiente. A esto lo llamamos tipo de área de memoria RAM "hilo privado".

Si el hilo está ejecutando un método Java, este contador registra la dirección de la instrucción de código de bytes de la máquina virtual que se está ejecutando;

Si se está ejecutando el método nativo, este contador está vacío (indefinido).

Esta área de memoria es la única área que no especifica ninguna condición de OutOfMemoryError en la especificación de la máquina virtual Java .

  1. Pila de máquina virtual Java (hilo privado):

Respuesta corta : supervisando la ejecución del programa Java, se crea cuando se crea el subproceso. Su vida útil sigue la vida útil del subproceso, y la memoria de la pila se libera cuando finaliza el subproceso. No hay ningún problema de recolección de basura para la pila, siempre que el subproceso termine la pila En cuanto a Over, el ciclo de vida es el mismo que el del subproceso y es privado para el subproceso. Los tipos básicos de variables y variables de referencia de objeto se asignan en la memoria de pila de la función.

Respuesta : El hilo es privado y el ciclo de vida es el mismo que el hilo. La pila de la máquina virtual describe el modelo de memoria de ejecución del método Java. Cuando se ejecuta cada método, se crea un marco de pila para almacenar la tabla de variables locales, operando pila y enlace dinámico., Método de exportación y otra información. El proceso desde la invocación hasta la finalización de cada método corresponde al proceso de empujar un marco de pila en la pila de la máquina virtual para que salga de la pila.

La tabla de variables locales almacena varios tipos básicos de datos conocidos en tiempo de compilación (booleano, byte, char, short, int, float, long, double), referencias a objetos y tipos returnAddress (apuntando a la dirección de una instrucción de código de bytes).

Los datos dobles y largos de 64 bits ocuparán dos espacios de tabla de variables locales (ranura), y los tipos de datos restantes solo ocuparán uno. El espacio de memoria requerido por la tabla de variables locales se asigna en tiempo de compilación. Al ingresar un método, la cantidad de espacio de variables locales que este método necesita asignar en el marco de la pila se determina completamente y la tabla de variables locales no se cambiará durante la ejecución del método tamaño.

En la especificación de la máquina virtual Java, hay dos excepciones para esta área: si la profundidad de la pila solicitada por el subproceso es mayor que la profundidad permitida por la máquina virtual, se lanzará una excepción Stack OverflowError; si la pila de la máquina virtual puede ser dinámicamente expandido, no se puede aplicar. Si hay suficiente memoria, se lanzará OutOfMemoryError.

  1. Pila de métodos locales (hilo privado):

Respuesta corta : La pila de métodos nativos sirve a los métodos nativos utilizados en la máquina virtual. La función del método nativo es integrar diferentes lenguajes de programación para Java. Su intención original es integrar programas C / C ++. Cuando nació Java, C / C ++ estaba desenfrenado., Si quiere afianzarse, debe tener un programa C / C ++ llamado, por lo que se abre un área especial en la memoria para procesar el código marcado como nativo.

Respuesta : Las funciones de la pila de métodos local y la pila de la máquina virtual son muy similares. La diferencia entre ellas es que la pila de la máquina virtual ejecuta los servicios de métodos Java (bytecode) para la máquina virtual, mientras que la pila de métodos local se usa en la máquina. Al servicio del método nativo. En la especificación de la máquina virtual, el idioma, el uso y la estructura de datos utilizados por los métodos en la pila de métodos locales no son obligatorios, por lo que la máquina virtual específica puede implementarlo libremente. Incluso algunas máquinas virtuales combinan directamente la pila de métodos locales y la pila de máquinas virtuales en una y, al igual que la pila de máquinas virtuales, también se lanzarán las excepciones Stack OverflowError y OutOfMemoryError.

  1. Montón de Java (intercambio de subprocesos):

Respuesta corta : el área del montón es la más grande en la JVM. Los objetos de aplicación y los datos existen en esta área. Esta área también es compartida por subprocesos. También es el área de recuperación principal de gc. Solo hay un almacenamiento dinámico para un Instancia de JVM. Se puede ajustar el tamaño de la memoria dinámica.

Respuesta : Para la mayoría de las aplicaciones, el espacio de pila es la parte más grande de la memoria jvm. El montón de Java es compartido por todos los subprocesos y se crea cuando se inicia la máquina virtual. El único propósito de esta área de memoria es almacenar instancias de objetos. Casi todas las instancias de objetos asignan memoria aquí. Esto se describe en la especificación de la máquina virtual Java: todas las instancias de objetos y las matrices deben asignarse en el montón, pero con el desarrollo del compilador JIT y la madurez gradual de las técnicas de análisis de escape, la asignación en la pila, la tecnología de optimización de reemplazo escalar será como como resultado de algunos cambios sutiles, todos los objetos se asignan en el montón y se vuelve menos absoluto.

El montón de Java es el área principal administrada por el recolector de basura, por lo que a menudo se denomina "montón de GC". Desde la perspectiva de la recuperación de la memoria, dado que los recolectores ahora adoptan básicamente algoritmos de recopilación generacional, los montones de Java también se pueden subdividir en: nueva generación y generación anterior; los más detallados incluyen el espacio Eden, el espacio From Survivor, el espacio To Survivor, etc. Desde la perspectiva de la asignación de memoria, el montón de Java compartido por subprocesos puede dividirse en múltiples búferes de asignación privados de subprocesos. Sin embargo, no importa cuál sea la división, no tiene nada que ver con el contenido del almacenamiento. No importa en qué área, el almacenamiento sigue siendo la instancia del objeto. El propósito de una mayor división es recuperar mejor la memoria o asignar memoria más rápido. (Si no hay memoria en el montón para completar la asignación de la instancia y el montón ya no se puede expandir, se lanzará una excepción OutOfMemoryError).

  1. Área de método (hilo compartido) :

Respuesta corta : todos los subprocesos se comparten como el montón, se utilizan principalmente para almacenar datos como información de clase, constantes, variables estáticas y código compilado por la JVM que ha sido cargado por la jvm.

Respuesta : El área de método es compartida por todos los subprocesos. Todos los campos y códigos de bytes de métodos, así como algunos métodos especiales como constructores, códigos de interfaz también se definen aquí. En pocas palabras, toda la información del método definido se almacena en esta área, que pertenece al intervalo compartido.

Las variables estáticas, las constantes, la información de clase (métodos de construcción / definiciones de interfaz) y los grupos de constantes de tiempo de ejecución se almacenan en el área de métodos; pero las variables de instancia se almacenan en la memoria del montón y no tienen nada que ver con el área de métodos.

En HotSpot lanzado en JDK1.7, el grupo de constantes de cadena se ha eliminado del área de métodos .

  1. Grupo constante (intercambio de subprocesos) :

Respuesta corta : el grupo de constantes de tiempo de ejecución es parte del área de métodos. Se utiliza para almacenar varios literales y referencias de símbolos generados durante la compilación. Su característica importante es dinámica. Es decir, el lenguaje Java no requiere que se generen constantes solo durante la compilación. También se pueden generar nuevas constantes durante el tiempo de ejecución. Estas constantes se colocan en el grupo de constantes de tiempo de ejecución.

Buena respuesta : el grupo de constantes de tiempo de ejecución es parte del área de métodos. Además de la información de descripción de la versión de la clase, los campos, los métodos y las interfaces en el archivo de la clase, también hay un grupo constante, que se utiliza para almacenar varios literales y referencias de símbolos generados durante la compilación. Esta parte del contenido será una vez cargada la clase, se almacena en el grupo de constantes de tiempo de ejecución en el área de métodos.

La máquina virtual Java tiene regulaciones estrictas sobre el formato de cada parte del archivo de clase, y el tipo de datos que se usa para almacenar cada byte debe cumplir con la especificación antes de que pueda ser reconocido por jvm. Pero para el grupo de constantes en tiempo de ejecución, la especificación de la máquina virtual Java no establece requisitos detallados.

Una característica importante del grupo de constantes en tiempo de ejecución es dinámica. El lenguaje Java no requiere que se generen constantes solo durante la compilación, es decir, el contenido del grupo de constantes en el archivo de clase no está preestablecido para ingresar al grupo de constantes en tiempo de ejecución del método Durante el tiempo de ejecución También es posible poner nuevas constantes en el grupo Esta característica se usa más comúnmente en el método intern () de la clase String.

Dado que el grupo de constantes de tiempo de ejecución es parte del área de método, naturalmente está limitado por la memoria del área de método. Cuando el grupo constante ya no pueda solicitar memoria, se lanzará una excepción outOfMemeryError .

En comparación con jdk 1.7, la mayor diferencia entre jdk 1.8 es que el área de metadatos reemplaza a la generación permanente . La naturaleza del metaespacio es similar a la generación permanente, y ambos son la realización del área de método en la especificación JVM. Sin embargo, la mayor diferencia entre el metaespacio y la generación permanente es que el espacio de metadatos no está en la máquina virtual, sino que usa la memoria local .

Pregunta 2: relacionada con la carga de clases (Sina Weibo)

Pregunta: ¿Cuáles son los procesos principales de las clases de carga de JVM y cómo cargarlos?

responder:

Respuesta corta : el proceso de carga de clases se refiere al proceso en el que la máquina virtual JVM carga la información de la clase en el archivo .class en la memoria y la analiza para generar el objeto de clase correspondiente. Se divide en cinco pasos: carga -> verificación -> preparación -> análisis -> inicialización. Cargando : cargue el archivo .class externo en la máquina virtual Java; verificación : asegúrese de que la información contenida en el archivo calss cargado cumpla con los requisitos de la máquina virtual Java; preparación : asigne memoria para las variables de clase y establezca el valor inicial de las variables de clase ; análisis : convierte referencias de símbolos en el grupo constante en referencias directas; Inicialización : inicializa variables de clase y bloques de código estático.

Respuesta Jing : Alerta temprana, contenido más extenso, ¡ prepárate !

Un archivo Java desde la finalización de la codificación hasta la ejecución final, generalmente incluye principalmente dos procesos: compilar y ejecutar

  • Compilación: el archivo java que hemos escrito se compila en código de bytes mediante el comando javac, que es lo que a menudo llamamos archivo .class.

  • Ejecutar: el archivo .class generado por la compilación se entrega a la máquina virtual Java (JVM) para su ejecución.

Lo que llamamos proceso de carga de clases se refiere al proceso en el que la máquina virtual JVM carga la información de la clase en el archivo .class en la memoria y la analiza para generar el objeto de clase correspondiente.

  • Proceso de carga de clases

Para un ejemplo simple, cuando la JVM está ejecutando un cierto fragmento de código, encuentra la clase A, pero no hay información sobre la clase A en la memoria en este momento, por lo que la JVM buscará la clase de clase A en el correspondiente Información del archivo de clase, y se carga en la memoria, esto es lo que llamamos el proceso de carga de clases.
Se puede ver que la JVM no carga todas las clases en la memoria al principio, sino que solo la carga cuando encuentra una determinada clase que necesita ejecutarse por primera vez y solo la carga una vez.

  • Carga de clases

El proceso de carga de clases se divide principalmente en tres partes: carga, vinculación e inicialización .

El enlace se puede subdividir en tres partes pequeñas: verificación, preparación y análisis .

  • carga

En pocas palabras, cargar se refiere a cargar archivos de códigos de bytes de clases de varias fuentes en la memoria a través de un cargador de clases.

Aquí hay dos puntos importantes:

Fuente de código de bytes: las fuentes de carga generales incluyen archivos .class compilados a partir de una ruta local, archivos .class en un paquete jar, compilación en tiempo real desde una red remota y proxy dinámico

Cargador de clases: generalmente incluye cargador de clases de inicio, cargador de clases extendido, cargador de clases de aplicación y cargador de clases definido por el usuario.

Nota: ¿Por qué hay un cargador de clases personalizado?
Por un lado, debido a que el código Java es fácil de descompilar, si necesita cifrar su propio código, puede cifrar el código compilado y luego descifrarlo implementando su propio cargador de clases personalizado y finalmente cargarlo.
Por otro lado, también es posible cargar código de fuentes no estándar, como desde una fuente de red, necesita implementar un cargador de clases usted mismo para cargar desde una fuente específica.

  • verificación

El objetivo principal es garantizar que el flujo de bytes cargado cumpla con las especificaciones de la máquina virtual y no cause errores de seguridad.

Incluyendo la verificación del formato de archivo, como si hay constantes no admitidas en las constantes. ¿Existe alguna información irregular o adicional en el expediente?

Para la verificación de metadatos, por ejemplo, si la clase hereda la clase modificada final. ¿Los campos y métodos de la clase entran en conflicto con la clase principal? ¿Existe una sobrecarga irrazonable?

Para la verificación del código de bytes, asegúrese de la racionalidad de la semántica del programa, como garantizar la racionalidad de la conversión de tipos.

¿Para la verificación de referencias de símbolos, como verificar si la clase correspondiente se puede encontrar a través del nombre completo en la referencia de símbolos? Compruebe si la clase actual puede acceder a la accesibilidad (privada, pública, etc.) en la referencia de símbolo.

  • Listo

Asigne principalmente memoria para variables de clase (nota, no variables de instancia) y asigne valores iniciales.

Se debe prestar especial atención al valor inicial, no al valor inicializado específicamente escrito en el código, sino al valor inicial predeterminado de la máquina virtual Java según los diferentes tipos de variables.

Por ejemplo, el valor inicial de los 8 tipos básicos es 0 por defecto; el valor inicial del tipo de referencia es nulo; el valor inicial de la constante es el valor establecido en el código,
tmp estático final = 456, luego el valor inicial de tmp en esta etapa es 456.

  • Analizando

El proceso de reemplazar referencias de símbolos en el grupo constante con referencias directas.

Dos puntos importantes:

Referencia de símbolo: una cadena, pero esta cadena proporciona cierta información que puede identificar de forma única un método, una variable y una clase.

Referencia directa: puede entenderse como una dirección de memoria o un desplazamiento. Por ejemplo, para los métodos de clase, las referencias directas a las variables de clase son punteros al área de método; por ejemplo, los métodos, las referencias directas a las variables de instancia son el desplazamiento desde el puntero principal de la instancia hasta la posición de la variable de instancia.

Por ejemplo, ahora llame al método hello (), la dirección de este método es 1234567, hola es una referencia simbólica y 1234567 es una referencia directa.

En la fase de análisis, la máquina virtual reemplaza todas las referencias simbólicas como nombres de clases, nombres de métodos y nombres de campos con direcciones de memoria específicas o compensaciones, es decir, referencias directas.

  • inicialización

Esta etapa es principalmente para inicializar la variable de clase, que es el proceso de ejecutar el constructor de la clase.  
En otras palabras, solo inicialice variables o declaraciones modificadas por static.  
Si la clase principal no se ha inicializado cuando se inicializa una clase, la clase principal se inicializará primero.    
Si se incluyen varias variables estáticas y bloques de código estático al mismo tiempo, se ejecutan en orden de arriba hacia abajo.

  • para resumir

El proceso de carga de clases es solo una parte del ciclo de vida de una clase. Antes de él, hay un proceso de compilación. Solo después de que se compila el código fuente, se puede obtener el archivo de código de bytes que puede ser cargado por la máquina virtual; después de eso , hay un proceso de uso de clase específico., Una vez completado el uso, se desinstalará durante el proceso de recolección de basura en el área de métodos. Si desea comprender el ciclo de vida completo de una clase de Java, puede verificar la información relevante en línea, por lo que no la repetiré aquí.

La tercera pregunta: relacionada con la memoria JVM (tecnología Yuncong)

Pregunta: ¿Habrá una fuga de memoria en Java? Descríbala brevemente

responder:

En teoría, Java no tiene problemas de pérdida de memoria debido al mecanismo de recolección de basura (GC) (esta es también una razón importante por la que Java se usa ampliamente en la programación del lado del servidor); sin embargo, en el desarrollo real, puede haber objetos inútiles pero alcanzables Estos objetos no pueden ser recuperados por el GC y se producen pérdidas de memoria .

Un ejemplo es que los objetos en la sesión de Hibernate (caché de nivel uno) están en un estado persistente y el recolector de basura no recuperará estos objetos, sin embargo, puede haber objetos basura inútiles en estos objetos.

El siguiente ejemplo también muestra una pérdida de memoria en Java:

package com.yuan_more;

import java.util.Arrays;
import java.util.EmptyStackException;

public class MyStack<T{
    private  T[] elements;
    private int size = 0;

    private static final int INIT_CAPACITY = 16;

    public MyStack(){
        elements = (T[]) new Object[INIT_CAPACITY];
    }

    public void push(T elem){
        ensureCapacity();
    }

    public T pop(){
        if(size == 0){
            throw new EmptyStackException();
        }
        return elements[-- size];
    }

    private void ensureCapacity() {
        if(elements.length == size){
            elements = Arrays.copyOf(elements,2 * size +1);
        }
    }
}

El código anterior implementa una estructura de pila (FILO). A primera vista, no parece haber ningún problema obvio. Incluso puede pasar las distintas pruebas unitarias que usted escribe.

Sin embargo, el método pop tiene un problema de pérdida de memoria. Cuando colocamos un objeto en la pila con el método pop, el objeto no se tratará como basura, incluso si el programa que usa la pila ya no hace referencia a estos objetos, porque la pila es mantenido internamente. Referencias obsoletas a estos objetos.

En los lenguajes que admiten la recolección de basura, las fugas de memoria están muy ocultas, que en realidad son retención de objetos inconscientes.

Si una referencia de objeto está reservada inconscientemente, entonces el recolector de basura no procesará este objeto, ni procesará otros objetos a los que hace referencia el objeto, incluso si solo hay algunos de esos objetos, puede causar que se excluyan muchos objetos. recolección de basura, tiene un impacto significativo en el rendimiento. En casos extremos, se activará la paginación del disco (memoria física y memoria virtual de los datos de intercambio del disco duro), e incluso se producirá un error de memoria externa.

Cuarta pregunta: relacionada con la recolección de basura (Didi Travel)

P: ¿Conoce GC? ¿Por qué hay una GC?

responder:

GC significa recolección de basura. El procesamiento de la memoria es un lugar donde los programadores son propensos a tener problemas. El olvido o la recolección incorrecta de la memoria puede causar inestabilidad o incluso el bloqueo del programa o sistema.

La función GC proporcionada por Java puede monitorear automáticamente si el objeto excede el alcance para lograr el propósito de la recuperación automática de memoria. El lenguaje Java no proporciona un método de operación de visualización para liberar la memoria asignada. Los programadores de Java no necesitan preocuparse por la administración de la memoria, porque el recolector de basura la administrará automáticamente .

要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,注意,只是请求,JVM何时进行垃圾回收具有不可预知性

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。

第五题:JVM内存相关(阿里)

问:Hotspot虚拟机中的堆为什么要有新生代和老年代?

答:

因为有的对象寿命长,有的对象寿命短。应该将寿命长的对象放在一个区,寿命短的对象放在一个区。不同的区采用不同的垃圾收集算法。寿命短的区清理频次高一点,寿命长的区清理频次低一点,提高效率。

所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区。加上老年代就这三个区

数据会首先分配到Eden区当中,当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。当Eden没有足够空间的时候就会触发jvm发起一次Minor GC。新生代垃圾回收采用的是复制算法

如果对象经过一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。如果老年代满了就执行:Full GC, 因为不经常执行,因此老年代垃圾回收采用了标记-整理(Mark-Compact)算法


Supongo que te gusta

Origin blog.51cto.com/14932245/2642839
Recomendado
Clasificación