choque bien! pérdidas de memoria primavera de arranque, la investigación en realidad tan difícil!

Autor | Ji Bing

Fuente | http://suo.im/5MABXL

fondo

Con el fin de gestionar mejor el proyecto, nos trasladaremos al grupo un marco MDP proyecto (basado en la primavera de arranque), entonces nos encontramos con que el sistema informado con frecuencia el uso Área de intercambio es demasiado alto anomalías. Me pidieron para ver la ayuda de la razón por la que las configuraciones están dentro de la memoria del montón 4G, pero el uso real de memoria física de hasta 7G, realmente no es normal. parámetros de JVM son "-XX: MetaspaceSize = 256M -XX: MaxMetaspaceSize = 256M -XX: + AlwaysPreTouch -XX: ReservedCodeCacheSize = 128m -XX: InitialCodeCacheSize = 128m, -Xss512k -Xmx4g -Xms4g, -XX: + UseG1GC -XX: G1HeapRegionSize = 4M", la memoria física utilizada realmente como se muestra a continuación:

comando top muestra las condiciones de memoria

proceso de investigación

1. herramientas Java posicionados área de nivel de memoria (la memoria de pila, la región del código y la aplicación DirectByteBuffer o utilizan unsafe.allocateMemory montón de memoria externa)

Añadir -XX en el proyecto: NativeMemoryTracking = detailJVM parámetro para reiniciar el proyecto, utilice el comando jcmd pid vista detallada VM.native_memory de la memoria distribuida de la siguiente manera:

Memoria vitrina jcmd

Encontramos memoria física comando muestra comprometida menos de memoria, porque la memoria contiene el comando de visualización jcmd memoria del montón, áreas de código, y la memoria mediante la aplicación unsafe.allocateMemory DirectByteBuffer, pero no incluye una pila externa otro código nativo (código C) memoria asignada . Así conjetura es que el uso de código nativo resultante de memoria de la aplicación.

Con el fin de prevenir aborto involuntario de la justicia, utilizo la distribución de vista de la memoria pmap, que se encuentra un gran número de dirección de 64M, y estos espacios de direcciones no son comandos jcmd espacio de direcciones dada en el interior, básicamente concluyó que estas 64M de memoria como resultado.

vitrina de memoria pmap

2. Uso de la herramienta de posicionamiento a nivel de sistema fuera montón de memoria

Debido a que he determinado básicamente que es causada por código nativo, y las herramientas de Java de nivel no son fáciles de solucionar estos problemas, sólo utilizar las herramientas a nivel de sistema para localizar el problema.

En primer lugar, los gperftools para localizar el problema

El método de uso puede hacer referencia a gperftools gperftools, gperftools monitoreo es como sigue:

gperftools monitoreo

Como puede verse en la figura: la memoria de la aplicación usando malloc hasta 3G después de haberlo publicado, y después se mantuvo a 700M-800M. La primera reacción fue autor: es el código nativo no utiliza malloc aplicar directamente usando la aplicación de mmap / BRK? (Principio Gperftools en formas de usar de vínculos dinámicos reemplaza asignador de memoria por defecto del sistema operativo (glibc).)

A continuación, utilizar strace para realizar un seguimiento de las llamadas al sistema

Debido a que estos no fueron rastreados utilizando la memoria gperftools, a continuación, utilizar directamente el comando "strace -f -e" BRK, mmap, munmap "-p pid" camino de OS peticiones de memoria de aplicaciones, pero no encontrado memoria de la aplicación sospechosa. monitor de strace como se muestra a continuación:

monitoreo strace

A continuación, el BGF sospechoso de volcado de memoria

Debido a que no existe un uso strace para realizar un seguimiento de la aplicación de memoria sospechosa, y luego mirar la situación de pensar acerca de la memoria. Es el uso directo del PIB -pid comando pid después de entrar en el BGF, y luego usar la memoria de volcado de memoria de comandos mem.bin StartAddress endAddressdump, que se puede encontrar StartAddress y endAddress desde / proc / PID / smaps en. A continuación, utilice cadenas mem.bin ver el contenido de la basura, de la siguiente manera:

gperftools monitoreo

Desde el punto de vista, el contenido como JAR descomprimido información de paquetes. información del paquete JAR debe ser leído al inicio del proyecto, a continuación, utilizar papel strace después del inicio del proyecto no es muy grande. Así strace se debe utilizar cuando se inició el proyecto, pero no completó después del inicio.

De nuevo, usando strace comienzo del proyecto para realizar un seguimiento de las llamadas al sistema

proyecto de uso strace comenzó a rastrear la llamada al sistema y se encontró que muchos de los 64M aplicación de espacio de memoria, los tiros son los siguientes:

monitoreo strace

Usando la dirección de aplicación espacial mmap PMAP corresponde a lo siguiente:

espacio de direcciones aplicación strace correspondiente a la pmap contenidos

Finalmente, jstack para ver el hilo correspondiente

Debido comando strace ha mostrado el hilo ID memoria de la aplicación. Directamente en la jstack comando pid para ver la pila de subprocesos, encontrar la correspondiente pila de subprocesos (nota 10 decimal y la conversión hexadecimal) de la siguiente manera:

aplicación Strace espacio de pila hilo

Aquí, básicamente, puede ver el problema: MCC (grupo de centros de distribución uniforme de Estados Unidos) que se utiliza para el paquete de barrido Reflexiones, parte inferior del resorte de arranque usado para JAR carga. Dado que el uso de descompresión JAR clase Inflater, es necesario utilizar montón de memoria externa, a continuación, utilizar Btrace para rastrear esta pila de clase de la siguiente manera:

rastro btrace pila

Y a continuación, ver el uso de MCC, donde se puede encontrar que la ruta del paquete de barrido, el valor predeterminado es escanear todos los paquetes. Modificar el código dispuestas de encaminamiento de paquetes de barrido, después de la liberación de la memoria de línea problemas resueltos.

3. ¿Por qué la pila de memoria no es liberado fuera?

Aunque el problema se ha resuelto, pero hay algunas preguntas:

  • ¿Por qué utilizar el antiguo marco no es ningún problema?

  • ¿Por qué no liberar el exterior memoria del montón?

  • ¿Por tamaño de la memoria es 64M, tamaño JAR no es tan grande, y todos ellos son del mismo tamaño?

  • ¿Por gperftools memoria de visualización final utilizada es de aproximadamente 700M, descomprimir el paquete en realidad no se aplica a utilizar la memoria malloc?

Con preguntas, miraba la primavera del cargador de arranque directa de que una pieza de código fuente. Primavera de arranque encontrar en InflaterInputStream Java JDK se envasaron y se utiliza Inflater, y la propia Inflater a paquetes JAR descomprimir necesidad de utilizar montón de memoria externa. El ZipInflaterInputStream clase después del envasado no libera Inflater llevó a cabo fuera de la memoria del montón. Así que creo que han encontrado la causa de este error inmediatamente retroalimentación a la comunidad de Primavera de arranque. Pero después de las votaciones, encontré Inflater implementa el objeto mismo método finalize, hay llamadas para liberar memoria de almacenamiento dinámico fuera de la lógica en este método. Que la liberación del resorte de arranque depende de la memoria fuera de la GC montón.

Cuando utilizo la vista del montón jmap dentro del objeto y se encontró que este objeto ha habido prácticamente ningún Inflater arriba. Así que es el momento para dudar de la GC, no llame a finalizar. Con esta duda, puse Inflater lleno sustituir sus envases Inflater en la primavera en el interior del cargador de arranque, RBI monitor de finalización, método finalize qué resultado ha sido llamado. Así que fui a leer Inflater código C correspondiente y encontré la inicialización de la memoria de la aplicación usando malloc, al final, también llamadas gratuitas para liberar memoria.

Por el momento, sólo puedo sospechoso que no hay tiempo libre real para liberar memoria, le dio el arranque de la primavera empaquetado InflaterInputStream para reemplazar Java JDK viene, encontraron que después de la sustitución, los problemas de memoria pueden ser resueltos.

En este momento, a continuación, volver a ver la distribución de memoria gperftools encuentran cuando se usa la primavera de arranque, el uso de memoria ha ido en aumento, un punto de repente dejó caer una gran cantidad de uso de la memoria (la cantidad directamente desde el 3G reduce a unos 700 metros). Este punto debe ser la causa de la GC, la memoria debe ser puesto en libertad, pero no vio cambios en la memoria el nivel del sistema operativo, no hay liberación para el sistema operativo, asignador de memoria que se encuentra en su poder?

Continuar para explorar, descubrir el asignador de memoria por defecto (glibc versión 2.12) Distribución y uso de la memoria gperftools diferencia dirección es obvio, direcciones 2.5G smaps pareció ser parte de Native Pila. dirección de memoria distribuida de la siguiente manera:

Gperftools muestran la distribución de direcciones de memoria

Esto es básicamente un asignador de memoria puede ser determinado en travesura; búsqueda una 64M bit glibc, encontraron 2,11 glibc sido introducido desde la agrupación de memoria (máquina de 64 bits es el tamaño de la memoria 64M) para cada hilo, lee como sigue:

simplista explicación bloque de memoria

Según el artículo, dijo MALLOC_ARENA_MAX para modificar las variables de entorno, no encontró ningún efecto. Ver tcmalloc (gperftools utilizan asignador de memoria) también utiliza una forma bloque de memoria.

Con el fin de verificar la agrupación de memoria está fuera del fantasma, lo que escribiría simplemente un asignador de memoria sin bloque de memoria. Utilice el comando gcc zjbmalloc.c -fPIC -shared -o zjbmalloc.so una biblioteca dinámica, entonces LD_PRELOAD exportación = zjbmalloc.so reemplazar asignador de memoria glibc. Demostración en el que el código es como sigue:

#include<sys/mman.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
//作者使用的64位机器,sizeof(size_t)也就是sizeof(long)
void* malloc ( size_t size )
{
   long* ptr = mmap( 0, size + sizeof(long), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0 );
   if (ptr == MAP_FAILED) {
  	return NULL;
   }
   *ptr = size;                     // First 8 bytes contain length.
   return (void*)(&ptr[1]);        // Memory that is after length variable
}

void *calloc(size_t n, size_t size) {
 void* ptr = malloc(n * size);
 if (ptr == NULL) {
	return NULL;
 }
 memset(ptr, 0, n * size);
 return ptr;
}
void *realloc(void *ptr, size_t size)
{
 if (size == 0) {
	free(ptr);
	return NULL;
 }
 if (ptr == NULL) {
	return malloc(size);
 }
 long *plen = (long*)ptr;
 plen--;                          // Reach top of memory
 long len = *plen;
 if (size <= len) {
	return ptr;
 }
 void* rptr = malloc(size);
 if (rptr == NULL) {
	free(ptr);
	return NULL;
 }
 rptr = memcpy(rptr, ptr, len);
 free(ptr);
 return rptr;
}

void free (void* ptr )
{
   if (ptr == NULL) {
	 return;
   }
   long *plen = (long*)ptr;
   plen--;                          // Reach top of memory
   long len = *plen;               // Read length
   munmap((void*)plen, len + sizeof(long));
}

Por enterrado en un asignador de costumbre que punto se encuentra, de hecho, después de que el programa inicie la aplicación fuera de la aplicación real de la pila de memoria está siempre entre 700M-800M, el uso gperftools memoria de la pantalla del monitor es de aproximadamente 700M-800M. Sin embargo, la diferencia en la memoria desde el punto de vista del sistema operativo, el proceso lleva mucho (en este caso a las afueras de la pila de memoria del monitor).

Hago un poco de prueba, utilizando un envase dispensador diferentes grados variables de barrido, la memoria ocupada por el siguiente:

comparación prueba de memoria

¿Por malloc costumbre aplicar 800M, finalmente ocupar la memoria física en 1,7 g ella?

la memoria debido a que el asignador de memoria personalizado se utiliza para asignar mmap memoria, Mmap asignar necesitaba redondea a un número entero de páginas, por lo que hay un enorme desperdicio de espacio. Al monitorear el número de páginas que se encuentran en la aplicación final de un 536k o así, de hecho, que se aplican a la memoria del sistema es igual a 512 K * 4k (tamaño de página) = 2G. ¿Por qué estos datos es mayor que 1,7 g ella?

Debido a que el sistema operativo se retrasa la asignación tomada manera, cuando la aplicación de la memoria del sistema a través de mmap, la memoria del sistema sólo se remite y sin asignación de memoria física real. Sólo cuando se utiliza en realidad el sistema genera un error de página, y luego redistribuido Página físico real.

resumen

diagrama de flujo

Todo el proceso de asignación de memoria como se muestra en la figura. paquete de barrido MCC es escanear todo el paquete JAR configuración predeterminada. Cuando el paquete de análisis, Spring Boot no busca para liberar fuera de la memoria de pila, lo que resulta en la fase de exploración, el uso de memoria montón externa sigue creciendo. Cuando la ocurrencia de la GC, Primavera de arranque dependen de finalizar el mecanismo para liberar el exterior memoria del montón, pero glibc Por motivos de rendimiento, la memoria realmente no va de nuevo al sistema operativo, sino que se quedara en el bloque de memoria, que llevó a la capa de aplicación tenido "pérdidas de memoria." Por lo tanto modificar la configuración de una ruta específica MCC paquete JAR para resolver el problema. El autor en el momento de la publicación de este artículo, descubre la última versión de la primavera de arranque (2.0.5.RELEASE) ha hecho cambios en ZipInflaterInputStream la iniciativa para liberar la memoria del montón ya no depende de GC fuera, de modo que la primavera de arranque de actualización a la última versión, este problema puede ser resuelto.

Publicado 50 artículos originales · ganado elogios 1706 · Vistas 2,22 millones +

Supongo que te gusta

Origin blog.csdn.net/zl1zl2zl3/article/details/105297617
Recomendado
Clasificación