Diagrama de estructura interna JVM
Las máquinas virtuales Java se dividen principalmente en cinco áreas: área de método, montón, pila Java, registro de PC y pila de métodos locales. Echemos un vistazo a algunos problemas importantes sobre la estructura JVM.
1. ¿Qué áreas se comparten? ¿Cuáles son privados?
La pila Java, la pila de métodos locales y el contador de programas se establecen y destruyen a medida que el hilo del usuario comienza y termina.
Cada hilo tiene estas áreas independientes. El área del método y el montón son compartidos por todos los hilos en todo el proceso JVM.
2. ¿Qué se guarda en el área del método? ¿Será reciclado?
El área del método no solo se guarda información y código del método. Al mismo tiempo, en una subregión llamada grupo constante de tiempo de ejecución, varias referencias simbólicas en la tabla constante en el archivo de clase, así como las referencias directas traducidas. Se accede a esta información a través de un objeto de clase en el montón como interfaz.
Aunque la información del tipo se almacena en el área del método, también se reciclará, pero las condiciones de reciclaje son relativamente estrictas:
(1) Todas las instancias de esta clase han sido recicladas
(2) El cargador de clases que carga esta clase ha sido reciclado
(3) El objeto de clase de esta clase no se hace referencia a ninguna parte (incluida la clase. Forname Reflection Access)
3. ¿No ha cambiado el contenido de la piscina constante en el área del método?
El grupo constante de tiempo de ejecución en el área de método guarda los datos en el grupo constante estático en el archivo de clase. Además de almacenar varias referencias literales y simbólicas generadas en el tiempo de compilación, también contiene referencias directas traducidas. Pero esto no significa que el grupo constante no cambiará durante el tiempo de ejecución. Por ejemplo, cuando se ejecuta, puede llamar al método interno de la cadena y poner nuevas constantes de cadena en la piscina.
paquete com.cdai.jvm; public class runtimeConstantPool {public static void main (string [] args) {String s1 = new String ("Hello"); Cadena s2 = nueva cadena ("Hello"); System.out.println ("antes de interno, S1 == S2:" + (S1 == S2)); s1 = s1.intern (); s2 = s2.intern (); System.out.println ("After Intern, S1 == S2:" + (S1 == S2)); }}
4. ¿Se asignan todas las instancias de objetos en el montón?
Con la madurez gradual de la tecnología de análisis de escape, las tecnologías de asignación de apilamiento y optimización de reemplazo escalar han hecho que "todos los objetos asignados en el montón" sean menos absolutos.
El llamado escape significa que cuando el puntero de un objeto se hace referencia por múltiples métodos o hilos, decimos que este puntero escapa.
En términos generales, los objetos Java se asignan en el montón, y solo los punteros del objeto se guardan en la pila. Suponiendo que una variable local no escape durante la ejecución del método (expuesto al exterior del método), se asignará directamente en la pila y luego continuará ejecutándose en la pila de llamadas. Después de la ejecución del método, el espacio de la pila se recicla y las variables locales también se reciclan. Esto reduce la asignación de una gran cantidad de objetos temporales en el montón y mejora la eficiencia del reciclaje de GC.
Además, el análisis de escape también omitirá bloqueos en variables locales que no han escapado, y omitir bloqueos de esta variable.
Al habilitar el método de análisis de escape, agregue los parámetros de inicio de JVM: -xx: +doScapeanalysis? Escapeanalysistest.
5. ¿Cuántas formas hay para acceder a objetos en el montón?
(1) Acceso directo al puntero
La referencia en la pila guarda un puntero a un objeto en el montón, y puede localizar el objeto de una sola vez, que tiene una velocidad de acceso más rápida.
Sin embargo, cuando los objetos se mueven en el montón (cada objetos a menudo se mueve durante la recolección de basura), el valor de la variable de puntero en la pila también debe cambiarse. Actualmente, JVM Hotspot adopta este método.
(2) Acceso indirecto al manejo
La referencia en la pila apunta a un mango en la piscina del mango, y se accede al objeto a través del valor en este mango. Por lo tanto, el mango es como un puntero secundario, que requiere dos posicionamiento para acceder al objeto, que es más lento que el posicionamiento directo del puntero, pero cuando el objeto se mueve en el montón, no hay necesidad de cambiar el valor referenciado en la pila.
Cómo desbordar JVM
Después de comprender el papel de las cinco áreas de memoria de las máquinas virtuales Java, continuemos aprendiendo en qué circunstancias se desbordarán estas áreas.
1. Configuración de parámetros de máquina virtual
-Xms: el tamaño inicial del montón, el valor predeterminado es 1/64 de memoria física (<1gb); predeterminado (se puede ajustar el parámetro MinheapFreeratio) Cuando la memoria de montón libre es inferior al 40%, el JVM aumentará el montón hasta el límite máximo de -xmx.
-Xmx: tamaño máximo del montón, predeterminado (se puede ajustar el parámetro maxheapFreeratio) Cuando la memoria del montón libre es superior al 70%, el JVM reducirá el montón hasta el límite mínimo de -xms.
-Xss: tamaño de pila para cada hilo. Después de JDK5.0, el tamaño de la pila de cada hilo es de 1 m, y en el pasado, el tamaño de la pila de cada hilo es de 256k. Debe ajustarse adecuadamente de acuerdo con el tamaño de memoria requerido del hilo de la aplicación. En la misma memoria física, reducir este valor puede generar más hilos. Sin embargo, el sistema operativo todavía tiene un límite en el número de hilos en un proceso y no se puede generar infinitamente, con valores de experiencia que van desde aproximadamente 3000 a 5000. Generalmente, aplicaciones pequeñas, si la pila no es muy profunda, 128k deberían ser suficientes. Para aplicaciones grandes, se recomiendan 256k. Esta opción tiene un gran impacto en el rendimiento y requiere pruebas estrictas.
-Xx: Permsize: Establezca el valor inicial de generación permanente (Perm Gen). El valor predeterminado es 1/64 de memoria física.
-Xx: maxPermsize: establezca el valor máximo de la generación persistente. 1/4 de memoria física.
2. Método de desbordamiento del área
Debido a que el área del método contiene la información relevante de la clase, cuando cargamos demasiadas clases, el área del método se desbordará. Aquí intentamos desbordar el área del método a través de dos métodos: JDK Dynamic Proxy y CGLIB Proxy.
2.1 proxy dinámico JDK
paquete com.cdai.jvm.overflow; import java.lang.reflect.invocationHandler; import java.lang.reflect.method; import java.lang.reflect.proxy; MethodAoverflow de clase pública {interfaz estática oomiinterface {} clase estática oomObject implementa oomiinterface {} clase estática oomobject2 implementa oomiinterface {} public static void main (string [] args) {fininal oomobject object = new oomObject (); while (true) {oomiinterface proxy = (oomiinterface) proxy.newproxyInStance (thread.currentThread (). getContextClassLoader (), oomObject.class.getInterfaces (), nuevo invocationHandler () {@Override Public Object Invoke (Proxy de objeto, método, método, objeto []) System.out.println ("Interceptor1 está funcionando"); System.out.println (proxy.getClass ()); System.out.println ("proxy1:" + proxy); Oomiinterface proxy2 = (oomiinterface) proxy.newproxyInstance (thread.currentThread (). GetContextClassLoader (), oomObject.class.getInterfaces (), nuevo invocationHandler () {@Override Public Object InvokE (Object Proxy, Método, Método, Object [] Shougs Shougs {System está funcionando "); return Method.invoke (objeto, args);}}); System.out.println (proxy2.getClass ()); System.out.println ("proxy2:" + proxy2); }}} Aunque seguimos llamando al método proxy.newinstance () para crear la clase proxy, el JVM no tiene un desbordamiento de memoria.
Cada llamada genera una instancia diferente de la clase proxy, pero el objeto de clase de la clase proxy no ha cambiado. ¿Es proxy?
¿La clase tiene un caché para el objeto de clase de la clase proxy? Las razones específicas se analizarán en detalle en el posterior "JDK Dynamic Proxy y CGLIB".
2.2 Agente CGLIB
CGLIB también almacenará en caché el objeto de clase de la clase proxy, pero podemos configurarlo para no almacenar en caché el objeto de clase.
De esta manera, el propósito de desbloquear el área del método se puede lograr creando repetidamente clases de proxy.
paquete com.cdai.jvm.overflow; import java.lang.reflect.method; importar net.sf.cglib.proxy.enhancer; importar net.sf.cglib.proxy.methodinterceptor; importar net.sf.cglib.proxy.methodproxy; MethodAoverflow2 de clase pública {Class estática OOMObject {} public static void main (string [] args) {while (true) {mejor mejor mejoral = new mejor (); potencador.setsuperClass (oomobject.class); potencador.setUsecache (falso); mejoran.setCallback (new MethodInterceptor () {@Override Public Object Intercept (Object obj, Method Method, Object [] Args, MethodProxy Proxy) lanza lando {return Method.invoke (obj, args);}}); Oomobject proxy = (oomobject) pothancer.create (); System.out.println (proxy.getClass ()); }}}
3. Desbordamiento del montón
El desbordamiento del montón es relativamente simple. Puede desbordar el montón creando un objeto de matriz grande.
paquete com.cdai.jvm.overflow; clase pública Heaproverflow {private static final int mb = 1024 * 1024; @Suppleswarnings ("no usado") público estático void main (string [] args) {byte [] bigMemory = new byte [1024 * mb]; }}
4. Overflow de pila
El desbordamiento de la pila también es común. A veces, cuando la llamada recursiva que escribimos no tiene la condición de terminación correcta, el método continuará recurriendo, la profundidad de la pila continuará aumentando y, finalmente, el desbordamiento de la pila ocurrirá.
paquete com.cdai.jvm.overflow; clase pública stackoverflow {private static int stackdepth = 1; public static void stackoverflow () {stackdepth ++; stackoverflow (); } public static void main (string [] args) {try {stackoverflow (); } Catch (Exception e) {System.err.println ("Profundidad de pila:" + StackDepth); E.PrintStackTrace (); }}}