Comprensión profunda del análisis de pila JVM (ocho) -java

Comprensión profunda de JVM (ocho) análisis de montón de Java La
sección anterior introdujo herramientas de monitoreo para JVM, incluido JPS puede ver todos los procesos de Java actuales, la pila de subprocesos de vista jstack puede ayudarlo a analizar si hay puntos muertos, etc., jmap puede ser exported El archivo de almacenamiento dinámico de Java se analiza en la herramienta MAT y así sucesivamente. Estas herramientas son muy útiles, pero su buen uso requiere un análisis práctico continuo. Este artículo presentará un caso de análisis de montón de Java utilizando la herramienta MAT.

Causas de memoria insuficiente (OOM)

Nuestra causa común de OOM (Error de memoria insuficiente) no es solo el desbordamiento de la memoria del montón, el desbordamiento de la memoria del montón es solo una de las situaciones de OOM, OOM también puede ocurrir en el metaespacio, la pila de subprocesos y la memoria directa.

A continuación se muestra la ocurrencia de OOM en cada área:

Montón OOM

public static void main(String[] args)
    {
        List<Byte[]> list=new ArrayList<Byte[]>();
        for(int i=0;i<100;i++){
            //构造1M大小的byte数值
            Byte[] bytes=new Byte[1024*1024];
            //将byte数组添加到list列表中,因为存在引用关系所以bytes数组不会被GC回收
            list.add(bytes);
        }
    }

El programa anterior establece la memoria de pila máxima en 50M y se ejecuta:
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Obviamente, el programa ocupará 100M de espacio de pila a través del bucle, lo que excede los 50M establecidos, por lo que se produce OOM de la memoria de pila.

Para este OOM, la solución es aumentar el espacio de memoria del montón, eliminar la relación de referencia cuando sea necesario en el desarrollo real, para que el recolector de basura pueda recuperar objetos inútiles lo antes posible.

Metaspace OOM

public static void main(String[] args) throws Exception
    {
        for(int i=0;i<1000;i++){
            //动态创建类
            Map<Object,Object> propertyMap = new HashMap<Object, Object>();  
            propertyMap.put("id", Class.forName("java.lang.Integer"));    
            CglibBean bean=new CglibBean(propertyMap);
            //给 Bean 设置值    
            bean.setValue("id", new Random().nextInt(100));  
            //打印 Bean的属性id
            System.out.println("id=" + bean.getValue("id"));    
        }
    }

El código anterior crea clases dinámicamente a través de Cglib y establece el tamaño del área de metadatos en 4M:
Inserte la descripción de la imagen aquí
debido a que el código crea clases en un bucle, una gran cantidad de metadatos de clase se almacena en el área de metadatos y excede el espacio establecido de 4M, por lo que el área de metadatos OOM se reporta:
Inserte la descripción de la imagen aquí
resolver el OOM La solución es aumentar el valor del parámetro MaxMetaspaceSize, o simplemente no configurar este parámetro Por defecto, la memoria disponible para el metaespacio estará limitada por la memoria local.

Apilar OOM

Al crear un nuevo subproceso, la JVM asignará memoria de pila a cada subproceso. Cuando se crean demasiados subprocesos, más memoria se ocupa. En este caso, OOM puede ocurrir:

public static void main(String[] args) throws Exception {
        //循环创建线程
        for (int i = 0; i < 1000000; i++) {
            new Thread(new Runnable() {
                    public void run() {
                        try {
                            //线程sleep时间足够长,保证线程不销毁
                            Thread.sleep(200000000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            System.out.println("created " + i + "threads");
        }
    }

Inserte la descripción de la imagen aquí
Obviamente, la solución a este OOM es reducir el número de subprocesos.

Memoria directa OOM

public static void main(String[] args) throws Exception {      
        for (int i = 0; i < 1000000; i++) {
            //申请堆外内存,这个内存是本地的直接内存,并非java堆内存
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*1024*1024);
            System.out.println("created "+i+" byteBuffer");
        }
    }

El método allocateDirect de ByteBuffer puede solicitar memoria directa. Cuando la memoria aplicada excede la memoria local disponible, informará OOM: La
Inserte la descripción de la imagen aquí
solución para este OOM es usar la memoria fuera del montón de manera apropiada y realizar explícitamente la recolección de basura si es necesario. (Es decir, ejecute System.gc () en el código;)

Agregado :,
直接内存是除Java虚拟机之外的内存 pero también puede ser utilizado por Java.

Puede asignar memoria directamente fuera de la máquina virtual Java llamando a un método local y luego manipular directamente la memoria a través de un objeto DirectByteBuffer almacenado en el montón de Java , sin primero copiar los datos en la memoria externa al montón y luego operar, mejorando así Mejore la eficiencia de las operaciones de datos.

El tamaño de la memoria directa no está controlado por la máquina virtual Java, pero dado que es memoria, se lanzará una excepción OOM cuando la memoria sea insuficiente.

Se comparan la memoria directa (memoria sin pila) y la memoria de pila.
El espacio de la aplicación de memoria directa consume un mayor rendimiento, especialmente cuando se solicita una determinada cantidad con frecuencia
. El rendimiento de la lectura y escritura de E / S de memoria directa es mejor que el de la pila normal memoria. La diferencia es obvia en el caso de operación

Comparación de la memoria fuera del montón y la memoria en el montón:
Ventajas :
  1 Reducir el trabajo de la recolección de basura, porque la recolección de basura suspenderá otro trabajo (puede usar múltiples subprocesos o división de tiempo, no puede sentirlo en absoluto)
  2 Velocidad Aumentar la velocidad de copia. Porque al vaciar del montón al remoto, primero se copiará a la memoria directa (memoria que no es del montón) y luego se enviará; mientras que la memoria fuera del montón equivale a omitir este trabajo.
Desventajas
1. La memoria fuera del montón es difícil de controlar Si la memoria se pierde, es difícil solucionar el problema
2. La memoria fuera del montón es relativamente inadecuada para almacenar objetos complejos. Generalmente son más adecuados los objetos simples o planos.

Uso de herramientas MAT

Cuando la aplicación java falla, es posible que necesitemos usar MAT para analizar el problema y averiguar la causa del problema. Aquí hay un caso para presentar el uso de MAT:

Preparación:
Usamos la herramienta jmap o jvisualvm para exportar un archivo de instantánea de pila desde el entorno de ejecución del programa por adelantado.
Use la herramienta MAT para abrir:
Inserte la descripción de la imagen aquí
Descubrimos que el objeto que ocupa más memoria es AppClassLoader. Sabemos que AppClassLoader es la clase que se usa para cargar la aplicación, así que miramos más a fondo los objetos a los que hace referencia.
Inserte la descripción de la imagen aquí
La siguiente figura muestra el uso de espacio del objeto al que hace referencia AppClassLoader. "Shallow Heap" representa el tamaño del montón superficial . El montón superficial es el espacio ocupado por la clase en sí, que es el tamaño de los metadatos de la propia clase. "Montón retenido" representa el tamaño del montón profundo . El montón profundo representa la suma del espacio ocupado por esta clase y otras clases a las que hace referencia. También significa la cantidad de espacio que se puede liberar después de que la clase se recolecta como basura . (Si se recicla la clase, el objeto al que se refiere se volverá inalcanzable y, por lo tanto, también se reciclará)
Inserte la descripción de la imagen aquí
Continúe mirando el objeto que ocupa el montón profundo más grande.
Inserte la descripción de la imagen aquí
Se puede ver en la figura anterior que la razón del montón profundo relativamente grande es que el programa contiene una ArrayList, que contiene una gran cantidad de objetos String, y cada objeto String tiene un tamaño de 80216 bytes.

Por lo tanto, el análisis de este montón es básicamente claro, debido a que el programa incluye una gran cantidad de objetos String, y están en ArrayList, la relación de referencia siempre existe, por lo que no se pueden recolectar basura, lo que resulta en OOM.

MAT otra descripción de la función

Además de la función MAT que usamos anteriormente, hay algunas funciones que también se usan con frecuencia.

Histogram:显示每个类使用情况以及占用空间大小。

Inserte la descripción de la imagen aquí
La figura anterior muestra que la clase char [] tiene 1026 objetos, ocupando 5967480 bytes de espacio. A través del análisis anterior, se concluye que el objeto String ocupa la mayor parte del espacio, y el almacenamiento interno del objeto Stirng usa char [] to It está almacenado, por lo que es comprensible que el tamaño del montón superficial de char [] se muestre aquí como 5967480 bytes.

Thread_overview:显示线程相关的信息。

Inserte la descripción de la imagen aquí

OQL:通过类似SQL语句的表达式查询对象信息。

Inserte la descripción de la imagen aquí
La figura anterior usa la declaración OQL para consultar el objeto String que coincide con 123 en la cadena.

Conclusión

Este artículo primero presenta varias situaciones de OOM en programas java y luego presenta el uso básico de MAT a través de casos simples.

Supongo que te gusta

Origin blog.csdn.net/eluanshi12/article/details/82757312
Recomendado
Clasificación