Red de conocimiento informático - Material del sitio web - Cómo escribir un fragmento de código Java para provocar una pérdida de memoria

Cómo escribir un fragmento de código Java para provocar una pérdida de memoria

R1: Las pérdidas de memoria (el código del programa no puede acceder a ciertos objetos, pero estos objetos permanecen en la memoria) se pueden generar fácilmente siguiendo estos pasos:

La aplicación crea un subproceso de larga duración (o utiliza un grupo de subprocesos, lo que provocará pérdidas de memoria más rápidas).

Este hilo carga una clase a través de un cargador de clases (personalizable).

Esta clase asigna un gran bloque de memoria (como un nuevo byte), almacena una referencia fuerte en una variable estática y luego almacena una referencia a sí misma en ThreadLocal. Asignar un nuevo byte de memoria adicional [1000000] es opcional (una pérdida de instancia de clase es suficiente), pero esto hará que la pérdida de memoria sea más rápida.

El hilo limpiará la clase personalizada o el cargador de clases que la cargó.

Repita los pasos anteriores.

No se puede acceder al espacio de almacenamiento en ThreadLocal sin una referencia a la clase y al cargador de clases. ThreadLocal contiene una referencia al objeto, que contiene una referencia a la clase y su cargador de clases, que contiene todas las referencias a las clases que carga, por lo que el GC no puede recuperar la memoria almacenada en ThreadLocal. En muchas implementaciones de JVM, las clases Java y los cargadores de clases se asignan directamente al área permgen sin realizar GC, lo que provocará pérdidas de memoria más graves.

Una variante de este patrón de fuga es que si vuelve a implementar con frecuencia una aplicación que usa ThreadLocal en cualquier forma, entonces el contenedor de la aplicación (como Tomcat) será propenso a sufrir pérdidas de memoria (porque, como se mencionó anteriormente, el El contenedor de la aplicación utiliza subprocesos, por lo que se utilizará un nuevo cargador de clases cada vez que se vuelva a implementar la aplicación).

A2:

Objeto de referencia de variable estática

clase MemorableClass {

lista ArrayList final estática = new ArrayList(100);

p>

}

Llama a una cadena larga String .intern()

String str=readString() // lee una cadena larga de cualquier base de datos fuente, cuadro de texto/jsp, etc.

// Esto colocará la cadena en el grupo de memoria y no se podrá eliminar de él

str.intern();

La secuencia abierta (archivo, red, etc.)

pruebe {

BufferedReader br = new BufferedReader(new FileReader(inputFile));

...

...

} catch (Excepción e) {

e.printStacktrace();

}

Conexión no cerrada

intenta {

Conexión conn = ConnectionFactory.

...

} captura (Excepción e) {

e .printStacktrace();

}

Áreas JVM inaccesibles para GC

Por ejemplo, memoria asignada a través de métodos nativos.

setAttribute("SOME_MAP", map);

Opciones de JVM incorrectas o inapropiadas

Por ejemplo, noclassgc de IBM JDK evita la recolección de basura de clases inútiles

A3: Si HashSet no implementa (o no implementa) hashCode() o equals() correctamente, se agregarán constantemente "copias" al conjunto. Si la colección no puede ignorar los elementos que debería ignorar, entonces su tamaño sólo puede crecer sin eliminar los elementos.

Si desea generar pares clave-valor incorrectos, haga lo siguiente:

class BadKey {

// no hashCode or equals();

p>

clave de cadena final pública;

pública BadKey() { this.String clave) { this.key = clave }

}

Map map = System.getProperties();

map.put(new BadKey("key"), "value"); // Incluso si su hilo muere, se perderá memoria.

R4: Además de los escenarios típicos de pérdida de memoria (como oyentes olvidados, referencias estáticas, claves incorrectas/modificadas en la tabla hash o bloqueo de subprocesos que no le permite finalizar su ciclo de vida), el A continuación se describen algunas situaciones menos obvias en las que Java sufre pérdidas de memoria, principalmente relacionadas con subprocesos.

Runtime.addShutdownHook no se elimina, e incluso si se usa removeShutdownHook, es posible que no se recicle debido a un error en la clase ThreadGroup para subprocesos no iniciados, lo que provoca una pérdida de memoria de ThreadGroup.

Los subprocesos se crean pero no se inician, la situación es la misma que la anterior

Al crear subprocesos que heredan de ContextClassLoader y AccessControlContext, utilizando ThreadGroup y InheritedThreadLocal, todas estas referencias tienen el potencial de pérdida de memoria. Como parte importante de todo el marco j.u.c.Executor (java.util.concurrent), el impacto en la interfaz ThreadFactory es tan obvio que muchos desarrolladores no son conscientes de sus peligros potenciales. Y muchas bibliotecas iniciarán hilos a pedido.

El almacenamiento en caché de ThreadLocal no es una buena práctica en muchas situaciones. Hay muchas implementaciones de caché simples basadas en ThreadLocal, pero pueden ocurrir fugas si el subproceso continúa ejecutando ContextClassLoader fuera de su vida útil esperada. No utilice el almacenamiento en caché ThreadLocal a menos que sea absolutamente necesario.

ThreadGroup.destroy() se llama cuando el ThreadGroup en sí no tiene subprocesos pero aún tiene un conjunto de subprocesos secundarios. Una pérdida de memoria evitará que un ThreadGroup se elimine de su ThreadGroup principal y evitará que se enumeren los ThreadGroups secundarios.

Con WeakHashMap, el valor hace referencia directa (indirectamente) a la clave, lo cual es difícil de detectar. Esto también se aplica a las clases que heredan Weak/SoftReference, que pueden contener fuertes referencias a objetos protegidos.

Usando .URL para descargar recursos. KeepAliveCache crea un nuevo hilo en el ThreadGroup del sistema, lo que provoca una pérdida de memoria en el cargador de clases de contexto del hilo actual. El hilo se crea en la primera solicitud sin un hilo en tiempo real, por lo que es probable que se produzcan fugas. (Este problema se solucionó en Java 7 y el código que crea el hilo eliminará sabiamente el cargador de clases de contexto).

Utilice un InflaterInputStream para pasar el nuevo java.util.zip.Inflater() en el constructor (como PNGImageDecoder) sin llamar al final del inflador(). Simplemente usar new es perfectamente seguro, pero si crea su propia clase como argumento del constructor y llama a close() en la secuencia sin cerrar el inflador, puede ocurrir una pérdida de memoria. En realidad, esto no es una pérdida de memoria porque el finalizador libera la memoria. Pero esto consume mucha memoria local y hace que oom_killer de Linux finalice el proceso. Entonces esto nos enseña una lección: liberar recursos locales lo antes posible.

Lo mismo ocurre con java.util.zip.Deflater, que es aún peor. Si crea usted mismo un Deflactor o Inflador, recuerde que debe llamar a end().