El uso de la memoria JVM está fuera de control

Tengo una aplicación web Tomcat que realiza algunas tareas de memoria y CPU intensas en nombre de los clientes. Esto es normal y es la funcionalidad deseada. Sin embargo, cuando ejecuto Tomcat, el uso de la memoria se dispara a lo largo del tiempo a más de 4.0 GB, momento en el que suelo matar el proceso ya que está jugando con todo lo demás que se ejecuta en mi máquina de desarrollo:

enter image description here

Pensé que había introducido inadvertidamente una pérdida de memoria con mi código, pero después de verificarlo con VisualVM, veo una historia diferente:

enter image description here

VisualVM muestra que el montón ocupa aproximadamente un GB de RAM, que es lo que configuré para hacer con CATALINA_OPTS="-Xms256m -Xmx1024" .

¿Por qué mi sistema ve este proceso como ocupando mucha memoria cuando, de acuerdo con VisualVM, no está ocupando casi nada?


Después de husmear un poco más, me doy cuenta de que si se ejecutan varios trabajos simultáneamente en las aplicaciones, la memoria no se libera. Sin embargo, si espero a que cada trabajo se complete antes de enviar otro a mi BlockingQueue atendida por un ExecutorService , la memoria se recicla de manera efectiva. ¿Cómo puedo depurar esto? ¿Por qué difieren la recolección de basura / reutilización de memoria?

    No puede controlar lo que desea controlar , -Xmx solo controla el Heap de Java, no controla el consumo de memoria nativa por parte de la JVM, que se consume de forma completamente diferente en función de la implementación. VisualVM solo le muestra qué es lo que resume el Heap, no muestra qué consume toda la JVM como memoria nativa como proceso de sistema operativo. Tendrá que usar herramientas de nivel de sistema operativo para ver eso, e informará números radicalmente diferentes, generalmente mucho más grandes que cualquier cosa que informe VisualVM, porque la JVM utiliza memoria nativa de una manera completamente diferente.

    Del siguiente artículo Gracias por la memoria (Comprender cómo la JVM usa memoria nativa en Windows y Linux)

    El mantenimiento del recolector de montones y basura utiliza memoria nativa que no puede controlar.

    Se requiere más memoria nativa para mantener el estado del sistema de administración de memoria que mantiene el almacenamiento dynamic de Java. Las estructuras de datos deben asignarse para rastrear el almacenamiento gratuito y registrar el progreso al recolectar la basura. El tamaño exacto y la naturaleza de estas estructuras de datos varía con la implementación, pero muchas son proporcionales al tamaño del montón.

    y el comstackdor JIT usa memoria nativa al igual que javac

    La comstackción de bytecode usa memoria nativa (de la misma manera que un comstackdor estático como gcc requiere memoria para ejecutarse), pero tanto la entrada (el bytecode) como la salida (el código ejecutable) del JIT también deben almacenarse en la memoria nativa. Las aplicaciones Java que contienen muchos métodos comstackdos JIT usan más memoria nativa que las aplicaciones más pequeñas.

    y luego tienes los cargadores de clases que usan memoria nativa

    Las aplicaciones Java están compuestas por clases que definen la estructura del objeto y la lógica del método. También usan clases de las bibliotecas de la clase de tiempo de ejecución de Java (como java.lang.String) y pueden usar bibliotecas de terceros. Estas clases deben almacenarse en la memoria durante el tiempo que estén siendo utilizadas. Cómo se almacenan las clases varía según la implementación.

    Ni siquiera comenzaré a citar la sección sobre Threads, creo que se entiende que -Xmx no controla lo que usted cree que controla, sino que controla el montón de JVM, no todo va en el montón de JVM, y el montón ocupa todo forma más memoria nativa de lo que especifiques para la administración y la contabilidad.

    Simple y llana, la JVM usa más memoria que la que se proporciona en -Xms y -Xmx y los otros parámetros de línea de comando.

    Aquí hay un artículo muy detallado sobre cómo la JVM asigna y administra la memoria , no es tan simple como lo que se espera de acuerdo con sus suposiciones en su pregunta, vale la pena una lectura exhaustiva.

    El tamaño de ThreadStack en muchas implementaciones tiene límites mínimos que varían según el sistema operativo y, a veces, la versión de JVM; la configuración de la secuencia de hilos se ignora si establece el límite por debajo del límite del sistema operativo nativo para la JVM o el sistema operativo (en ocasiones, se debe establecer ulimit on * nix). Otras opciones de línea de comandos funcionan de la misma manera, estableciendo valores predeterminados de forma silenciosa en valores más altos cuando se suministran valores demasiado pequeños. No asum que todos los valores pasados ​​representan lo que realmente se usa.

    Los Classloaders, y Tomcat tiene más de uno, consumen mucha memoria que no está documentada fácilmente. El JIT consume mucha memoria, intercambiando espacio por tiempo, lo que es una buena compensación la mayor parte del tiempo.

    También debe verificar el uso de la CPU y el recolector de basura.
    Es posible que las pausas de recolección de basura y la CPU gc consumn aún más ralentizar su máquina.