Creando una pérdida de memoria con Java

Acabo de tener una entrevista y me pidieron que creara una pérdida de memoria con Java. No hace falta decir que me sentí muy tonto sin tener ni idea de cómo empezar a crear uno.

¿Qué sería un ejemplo?

Esta es una buena forma de crear una verdadera pérdida de memoria (objetos inaccesibles ejecutando código pero aún almacenados en la memoria) en Java puro:

  1. La aplicación crea un hilo de larga ejecución (o utiliza un grupo de subprocesos para filtrar incluso más rápido).
  2. El hilo carga una clase a través de un ClassLoader (opcionalmente personalizado).
  3. La clase asigna una gran cantidad de memoria (por ejemplo, new byte[1000000] ), almacena una referencia fuerte en un campo estático y luego almacena una referencia a sí mismo en un ThreadLocal. La asignación de la memoria extra es opcional (la fuga de la instancia de Clase es suficiente), pero hará que la fuga funcione mucho más rápido.
  4. El subproceso borra todas las referencias a la clase personalizada o al ClassLoader del que se cargó.
  5. Repetir.

Esto funciona porque ThreadLocal guarda una referencia al objeto, que guarda una referencia a su Clase, que a su vez guarda una referencia a su ClassLoader. El ClassLoader, a su vez, mantiene una referencia a todas las clases que ha cargado.

(Fue peor en muchas implementaciones de JVM, especialmente antes de Java 7, porque Classes y ClassLoaders se asignaron directamente a permgen y nunca se obtuvieron GC. Sin embargo, independientemente de cómo maneje JVM la descarga de clases, un ThreadLocal evitará una Objeto de clase de ser reclamado.)

Una variación de este patrón es la razón por la que los contenedores de aplicaciones (como Tomcat) pueden filtrar la memoria como un tamiz si con frecuencia vuelves a desplegar aplicaciones que usan ThreadLocals de alguna manera. (Dado que el contenedor de aplicaciones usa Threads como se describe, y cada vez que vuelva a implementar la aplicación, se usa un nuevo ClassLoader).

Actualización : debido a que mucha gente sigue pidiéndolo, aquí hay un código de ejemplo que muestra este comportamiento en acción .

Campo estático que contiene referencia de objeto [esp campo final]

 class MemorableClass { static final ArrayList list = new ArrayList(100); } 

Llamar a String.intern() en una cadena larga

 String str=readString(); // read lengthy string any source db,textbox/jsp etc.. // This will place the string in memory pool from which you can't remove str.intern(); 

(No cerrado) abrir streams (archivo, red, etc.)

 try { BufferedReader br = new BufferedReader(new FileReader(inputFile)); ... ... } catch (Exception e) { e.printStacktrace(); } 

Conexiones no cerradas

 try { Connection conn = ConnectionFactory.getConnection(); ... ... } catch (Exception e) { e.printStacktrace(); } 

Áreas que son inalcanzables del recolector de elementos no utilizados de JVM , como la memoria asignada a través de métodos nativos

En las aplicaciones web, algunos objetos se almacenan en el ámbito de la aplicación hasta que la aplicación se detiene o elimina explícitamente.

 getServletContext().setAttribute("SOME_MAP", map); 

Opciones de JVM incorrectas o incorrectas , como la opción noclassgc en IBM JDK que impide la recolección de basura de la clase no utilizada

Ver configuración jdk de IBM .

Una cosa simple que hacer es usar un HashSet con un hashCode() o equals() incorrecto (o inexistente equals() , y luego seguir agregando “duplicados”. En lugar de ignorar los duplicados como debería, el conjunto solo crecerá y no podrá eliminarlos.

Si desea que estas claves / elementos malos se queden, puede usar un campo estático como

 class BadKey { // no hashCode or equals(); public final String key; public BadKey(String key) { this.key = key; } } Map map = System.getProperties(); map.put(new BadKey("key"), "value"); // Memory leak even if your threads die. 

A continuación, habrá un caso no obvio en el que se filtra Java, además del caso estándar de oyentes olvidados, referencias estáticas, claves falsas / modificables en hashmaps, o simplemente hilos atrapados sin posibilidad de finalizar su ciclo de vida.

  • File.deleteOnExit() – siempre File.deleteOnExit() la cadena, si la cadena es una subcadena, la fuga es aún peor (el char subyacente [] también se filtró) en la subcadena Java 7 también copia el char[] , por lo que el último no se aplica ; @Daniel, sin necesidad de votos, sin embargo.

Me concentraré en los hilos para mostrar el peligro de los hilos no administrados en su mayoría, ni siquiera deseo tocar el columpio.

  • Runtime.addShutdownHook y no eliminar … y luego, incluso con removeShutdownHook debido a un error en la clase ThreadGroup con respecto a los hilos no iniciados que puede no recogerse, se filtra efectivamente el ThreadGroup. JGroup tiene la fuga en GossipRouter.

  • Crear, pero no iniciar, un Thread entra en la misma categoría que arriba.

  • Al crear un hilo hereda el ContextClassLoader y el AccessControlContext , más el ThreadGroup y cualquier InheritedThreadLocal , todas esas referencias son filtraciones potenciales, junto con las clases completas cargadas por el cargador de clases y todas las referencias estáticas, y ja-ja. El efecto es especialmente visible con todo el framework jucExecutor que cuenta con una interfaz ThreadFactory muy simple, sin embargo, la mayoría de los desarrolladores no tienen ni idea del peligro que acecha. Además, muchas bibliotecas inician subprocesos a petición (demasiadas bibliotecas populares de la industria).

  • Cachés de ThreadLocal ; esos son malvados en muchos casos. Estoy seguro de que todo el mundo ha visto bastantes cachés simples basados ​​en ThreadLocal, y las malas noticias: si el hilo sigue funcionando más de lo esperado, la vida del contexto ClassLoader es una pequeña filtración pura y agradable. No use cachés de ThreadLocal a menos que realmente lo necesite.

  • Llamando a ThreadGroup.destroy() cuando el ThreadGroup no tiene subprocesos, pero todavía mantiene hijos ThreadGroups. Una fuga deficiente que evitará que ThreadGroup se elimine de su principal, pero todos los elementos secundarios no se podrán enumerar.

  • El uso de WeakHashMap y el valor (en) hace referencia directamente a la clave. Es difícil encontrar uno sin un montón de basura. Esto se aplica a todas las Weak/SoftReference extendidas que podrían mantener una referencia dura al objeto guardado.

  • Usando java.net.URL con el protocolo HTTP (S) y cargando el recurso desde (!). Este es especial, KeepAliveCache crea un nuevo hilo en el sistema ThreadGroup que filtra el cargador de clases de contexto del hilo actual. El hilo se crea en la primera solicitud cuando no existe hilo vivo, por lo que puede tener suerte o simplemente tener una fuga. La fuga ya está solucionada en Java 7 y el código que crea el hilo elimina correctamente el cargador de clases de contexto. Hay pocos casos más ( como ImageFetcher , también fijado ) de crear hilos similares.

  • Usando InflaterInputStream pasando el new java.util.zip.Inflater() en el constructor ( PNGImageDecoder por ejemplo) y no llamando a end() del inflador. Bueno, si pasas el constructor con algo new , no hay posibilidad … Y sí, al llamar a close() en la transmisión no se cierra el inflador si se pasa manualmente como parámetro de constructor. Esta no es una verdadera filtración ya que sería liberada por el finalizador … cuando lo considere necesario. Hasta ese momento se come tanto la memoria nativa que puede causar que Linux oom_killer mate el proceso con impunidad. El problema principal es que la finalización en Java es muy poco confiable y G1 empeoró hasta 7.0.2. Moraleja de la historia: libera recursos nativos tan pronto como puedas; el finalizador es demasiado pobre.

  • El mismo caso con java.util.zip.Deflater . Este es mucho peor ya que Deflater tiene mucha memoria en Java, es decir, siempre usa 15 bits (máximo) y 8 niveles de memoria (9 es máximo) asignando varios cientos de KB de memoria nativa. Afortunadamente, Deflater no se usa ampliamente y, que yo sepa, JDK no contiene ningún uso indebido. Siempre llame a end() si crea manualmente un Deflater o un Inflater . La mejor parte de los dos últimos: no puede encontrarlos a través de las herramientas normales de creación de perfiles disponibles.

(Puedo agregar algunas pérdidas de tiempo más que he encontrado a pedido).

Buena suerte y cuídate; ¡las filtraciones son malvadas!

La mayoría de los ejemplos aquí son “demasiado complejos”. Son casos extremos. Con estos ejemplos, el progtwigdor cometió un error (como no redefinir equals / hashcode), o ha sido mordido por un caso de esquina de la JVM / JAVA (carga de clase con estática …). Creo que ese no es el tipo de ejemplo que quiere un entrevistador o incluso el caso más común.

Pero hay casos realmente más simples para las pérdidas de memoria. El recolector de basura solo libera lo que ya no se referencia. Nosotros, como desarrolladores de Java, no nos importa la memoria. Lo asignamos cuando es necesario y lo liberamos automáticamente. Multa.

Pero cualquier aplicación de larga duración tiende a tener estado compartido. Puede ser cualquier cosa, estática, singleton … A menudo, las aplicaciones no triviales tienden a hacer gráficos de objetos complejos. Solo olvidando establecer una referencia a nulo o más a menudo olvidando eliminar un objeto de una colección es suficiente para hacer una pérdida de memoria.

Por supuesto, todo tipo de oyentes (como los oyentes UI), cachés o cualquier estado compartido de larga duración tienden a producir pérdida de memoria si no se manejan adecuadamente. Lo que se debe entender es que este no es un caso de esquina de Java, o un problema con el recolector de basura. Es un problema de diseño. Diseñamos que agreguemos un oyente a un objeto de larga duración, pero no eliminamos el oyente cuando ya no se necesita. Almacenamos objetos en caché, pero no tenemos ninguna estrategia para eliminarlos del caché.

Es posible que tengamos un gráfico complejo que almacena el estado anterior que necesita un cálculo. Pero el estado previo está vinculado al estado anterior y así sucesivamente.

Como si tuviéramos que cerrar conexiones o archivos SQL. Necesitamos establecer referencias adecuadas para anular y eliminar elementos de la colección. Tendremos estrategias de almacenamiento en caché adecuadas (tamaño de memoria máximo, número de elementos o temporizadores). Todos los objetos que permiten que un oyente sea notificado deben proporcionar un método addListener y removeListener. Y cuando estos notificadores ya no se usan, deben borrar su lista de oyentes.

Una fuga de memoria es realmente posible y es perfectamente predecible. No es necesario contar con funciones especiales de idiomas o casos de esquina. Las memory leaks son un indicador de que tal vez falta algo o incluso de problemas de diseño.

La respuesta depende completamente de lo que el entrevistador pensó que estaban preguntando.

¿Es posible en la práctica hacer que Java escape? Por supuesto que sí, y hay muchos ejemplos en las otras respuestas.

Pero hay múltiples meta preguntas que pueden haberse preguntado.

  • ¿Es teóricamente una implementación Java “perfecta” vulnerable a fugas?
  • ¿El candidato entiende la diferencia entre teoría y realidad?
  • ¿El candidato entiende cómo funciona la recolección de basura?
  • ¿O cómo se supone que la recolección de basura funciona en un caso ideal?
  • ¿Saben que pueden llamar a otros idiomas a través de interfaces nativas?
  • ¿Saben que pierde memoria en esos otros idiomas?
  • ¿El candidato sabe incluso qué es la administración de memoria y qué está sucediendo detrás de la escena en Java?

Estoy leyendo su meta-pregunta como “Cuál es la respuesta que podría haber usado en esta situación de entrevista”. Y, por lo tanto, me centraré en las habilidades de entrevista en lugar de Java. Creo que es más probable que repita la situación de no saber la respuesta a una pregunta en una entrevista que estar en un lugar en el que necesita saber cómo hacer que Java escape. Entonces, con suerte, esto ayudará.

Una de las habilidades más importantes que puede desarrollar para entrevistar es aprender a escuchar activamente las preguntas y trabajar con el entrevistador para extraer su intención. Esto no solo te permite responder a su pregunta de la manera que ellos quieren, sino que también muestra que tienes algunas habilidades vitales de comunicación. Y cuando se trata de elegir entre muchos desarrolladores igualmente talentosos, contrataré a quien escuche, piense y entienda antes de responder siempre.

El siguiente es un ejemplo bastante inútil, si no entiende JDBC . O al menos cómo JDBC espera que un desarrollador cierre las instancias Connection , Statement y ResultSet antes de descartarlas o perder referencias a ellas, en lugar de confiar en la implementación de finalize .

 void doWork() { try { Connection conn = ConnectionFactory.getConnection(); PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query ResultSet rs = stmt.executeQuery(); while(rs.hasNext()) { ... process the result set } } catch(SQLException sqlEx) { log(sqlEx); } } 

El problema con lo anterior es que el objeto Connection no está cerrado y, por lo tanto, la conexión física permanecerá abierta, hasta que el recolector de basura aparezca y vea que no está disponible. GC invocará el método de finalize , pero hay controladores JDBC que no implementan la finalize , al menos no de la misma manera que se implementa Connection.close . El comportamiento resultante es que, si bien la memoria se recuperará debido a que se recostackn objetos inalcanzables, es posible que los recursos (incluida la memoria) asociados con el objeto Connection no se reclamen.

En tal caso, donde el método de finalize la Connection no lo limpia todo, uno podría encontrar que la conexión física al servidor de la base de datos durará varios ciclos de recolección de basura, hasta que el servidor de la base de datos descubra que la conexión no está activa ( si lo hace), y debe ser cerrado.

Incluso si el controlador JDBC implementara finalize , es posible que se generen excepciones durante la finalización. El comportamiento resultante es que cualquier memoria asociada con el objeto ahora “inactivo” no será reclamada, ya que se garantiza que el finalize se invoque solo una vez.

El escenario anterior de encontrar excepciones durante la finalización de un objeto está relacionado con otro escenario que podría provocar una pérdida de memoria: la resurrección del objeto. La resurrección de objetos a menudo se hace intencionalmente al crear una referencia fuerte al objeto desde su finalización, desde otro objeto. Cuando la resurrección de objetos es mal utilizada, se producirá una pérdida de memoria en combinación con otras fonts de pérdidas de memoria.

Hay muchos más ejemplos que puedes conjurar, como

  • Administrar una instancia de List en la que solo está agregando elementos a la lista y no está eliminándola (aunque debería deshacerse de los elementos que ya no necesita) o
  • Abrir Socket s o File s, pero no cerrarlos cuando ya no sean necesarios (similar al ejemplo anterior relacionado con la clase Connection ).
  • No descarga Singletons cuando se baja una aplicación Java EE. Aparentemente, el Classloader que cargó la clase singleton retendrá una referencia a la clase y, por lo tanto, nunca se recostackrá la instancia singleton. Cuando se implementa una nueva instancia de la aplicación, generalmente se crea un nuevo cargador de clases, y el anterior cargador de clases continuará existiendo debido al singleton.

Probablemente uno de los ejemplos más simples de una posible pérdida de memoria y cómo evitarlo es la implementación de ArrayList.remove (int):

 public E remove(int index) { RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved); elementData[--size] = null; // (!) Let gc do its work return oldValue; } 

Si lo estuviera implementando usted mismo, ¿habría pensado borrar el elemento de matriz que ya no se usa ( elementData[--size] = null )? Esa referencia podría mantener vivo un gran objeto …

Cada vez que mantiene referencias a objetos que ya no necesita, tiene una pérdida de memoria. Consulte Manejo de memory leaks en progtwigs Java para ver ejemplos de cómo las pérdidas de memoria se manifiestan en Java y qué puede hacer al respecto.

Puede hacer que la memoria se escape con la clase sun.misc.Unsafe . De hecho, esta clase de servicio se utiliza en diferentes clases estándar (por ejemplo, en clases java.nio ). No puede crear una instancia de esta clase directamente , pero puede usar la reflexión para hacer eso .

El código no se comstack en Eclipse IDE: comstackrlo con el comando javac (durante la comstackción recibirás advertencias)

 import java.lang.reflect.Constructor; import java.lang.reflect.Field; import sun.misc.Unsafe; public class TestUnsafe { public static void main(String[] args) throws Exception{ Class unsafeClass = Class.forName("sun.misc.Unsafe"); Field f = unsafeClass.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); System.out.print("4..3..2..1..."); try { for(;;) unsafe.allocateMemory(1024*1024); } catch(Error e) { System.out.println("Boom :)"); e.printStackTrace(); } } } 

Puedo copiar mi respuesta desde aquí: ¿La forma más fácil de causar pérdida de memoria en Java?

“Una pérdida de memoria, en ciencias de la computación (o fugas, en este contexto), ocurre cuando un progtwig de computadora consume memoria pero no puede volver a liberarla al sistema operativo”. (Wikipedia)

La respuesta fácil es: no puedes. Java hace gestión automática de memoria y liberará recursos que no son necesarios para usted. No puedes evitar que esto suceda. SIEMPRE podrá liberar los recursos. En los progtwigs con gestión de memoria manual, esto es diferente. No puedes obtener algo de memoria en C usando malloc (). Para liberar la memoria, necesita el puntero que devuelve malloc y llamar a free () en él. Pero si ya no tiene el puntero (sobrescrito, o se excedió la vida útil), desafortunadamente no puede liberar esta memoria y, por lo tanto, tiene una pérdida de memoria.

Todas las otras respuestas hasta ahora están en mi definición, no son realmente pérdidas de memoria. Todos apuntan a llenar la memoria con cosas sin sentido muy rápido. Pero en cualquier momento, aún se podía desreferenciar los objetos que se crearon y liberar así la memoria -> SIN FUGAS. Sin embargo, la respuesta de acconrad es bastante aproximada, como tengo que admitir, ya que su solución es simplemente “colgar” al recolector de basura al forzarlo en un bucle infinito).

La respuesta larga es: puede obtener una pérdida de memoria escribiendo una biblioteca para Java utilizando el JNI, que puede tener administración de memoria manual y así tener pérdidas de memoria. Si llama a esta biblioteca, su proceso java perderá memoria. Or, you can have bugs in the JVM, so that the JVM looses memory. There are probably bugs in the JVM, there may even be some known ones since garbage collection is not that trivial, but then it’s still a bug. By design this is not possible. You may be asking for some java code that is effected by such a bug. Sorry I don’t know one and it might well not be a bug anymore in the next Java version anyway.

Here’s a simple/sinister one via http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 .

 public class StringLeaker { private final String muchSmallerString; public StringLeaker() { // Imagine the whole Declaration of Independence here String veryLongString = "We hold these truths to be self-evident..."; // The substring here maintains a reference to the internal char[] // representation of the original string. this.muchSmallerString = veryLongString.substring(0, 1); } } 

Because the substring refers to the internal representation of the original, much longer string, the original stays in memory. Thus, as long as you have a StringLeaker in play, you have the whole original string in memory, too, even though you might think you’re just holding on to a single-character string.

The way to avoid storing an unwanted reference to the original string is to do something like this:

 ... this.muchSmallerString = new String(veryLongString.substring(0, 1)); ... 

For added badness, you might also .intern() the substring:

 ... this.muchSmallerString = veryLongString.substring(0, 1).intern(); ... 

Doing so will keep both the original long string and the derived substring in memory even after the StringLeaker instance has been discarded.

A common example of this in GUI code is when creating a widget/component and adding a listener to some static/application scoped object and then not removing the listener when the widget is destroyed. Not only do you get a memory leak, but also a performance hit as when whatever you are listening to fires events, all your old listeners are called too.

Take any web application running in any servlet container (Tomcat, Jetty, Glassfish, whatever…). Redeploy the app 10 or 20 times in a row (it may be enough to simply touch the WAR in the server’s autodeploy directory.

Unless anybody has actually tested this, chances are high that you’ll get an OutOfMemoryError after a couple of redeployments, because the application did not take care to clean up after itself. You may even find a bug in your server with this test.

The problem is, the lifetime of the container is longer than the lifetime of your application. You have to make sure that all references the container might have to objects or classes of your application can be garbage collected.

If there is just one reference surviving the undeployment of your web app, the corresponding classloader and by consequence all classes of your web app cannot be garbage collected.

Threads started by your application, ThreadLocal variables, logging appenders are some of the usual suspects to cause classloader leaks.

Maybe by using external native code through JNI?

With pure Java, it is almost impossible.

But that is about a “standard” type of memory leak, when you cannot access the memory anymore, but it is still owned by the application. You can instead keep references to unused objects, or open streams without closing them afterwards.

I have had a nice “memory leak” in relation to PermGen and XML parsing once. The XML parser we used (I can’t remember which one it was) did a String.intern() on tag names, to make comparison faster. One of our customers had the great idea to store data values not in XML attributes or text, but as tagnames, so we had a document like:

  <1>bla <2>foo ...  

In fact, they did not use numbers but longer textual IDs (around 20 characters), which were unique and came in at a rate of 10-15 million a day. That makes 200 MB of rubbish a day, which is never needed again, and never GCed (since it is in PermGen). We had permgen set to 512 MB, so it took around two days for the out-of-memory exception (OOME) to arrive…

I recently encountered a memory leak situation caused in a way by log4j.

Log4j has this mechanism called Nested Diagnostic Context(NDC) which is an instrument to distinguish interleaved log output from different sources. The granularity at which NDC works is threads, so it distinguishes log outputs from different threads separately.

In order to store thread specific tags, log4j’s NDC class uses a Hashtable which is keyed by the Thread object itself (as opposed to say the thread id), and thus till the NDC tag stays in memory all the objects that hang off of the thread object also stay in memory. In our web application we use NDC to tag logoutputs with a request id to distinguish logs from a single request separately. The container that associates the NDC tag with a thread, also removes it while returning the response from a request. The problem occurred when during the course of processing a request, a child thread was spawned, something like the following code:

 pubclic class RequestProcessor { private static final Logger logger = Logger.getLogger(RequestProcessor.class); public void doSomething() { .... final List hugeList = new ArrayList(10000); new Thread() { public void run() { logger.info("Child thread spawned") for(String s:hugeList) { .... } } }.start(); } } 

So an NDC context was associated with inline thread that was spawned. The thread object that was the key for this NDC context, is the inline thread which has the hugeList object hanging off of it. Hence even after the thread finished doing what it was doing, the reference to the hugeList was kept alive by the NDC context Hastable, thus causing a memory leak.

I thought it was interesting that no one used the internal class examples. If you have an internal class; it inherently maintains a reference to the containing class. Of course it is not technically a memory leak because Java WILL eventually clean it up; but this can cause classes to hang around longer than anticipated.

 public class Example1 { public Example2 getNewExample2() { return this.new Example2(); } public class Example2 { public Example2() {} } } 

Now if you call Example1 and get an Example2 discarding Example1, you will inherently still have a link to an Example1 object.

 public class Referencer { public static Example2 GetAnExample2() { Example1 ex = new Example1(); return ex.getNewExample2(); } public static void main(String[] args) { Example2 ex = Referencer.GetAnExample2(); // As long as ex is reachable; Example1 will always remain in memory. } } 

I’ve also heard a rumor that if you have a variable that exists for longer than a specific amount of time; Java assumes that it will always exist and will actually never try to clean it up if cannot be reached in code anymore. But that is completely unverified.

What’s a memory leak:

  • It’s caused by a bug or bad design.
  • It’s a waste of memory.
  • It gets worse over time.
  • The garbage collector cannot clean it.

Typical example:

A cache of objects is a good starting point to mess things up.

 private static final Map myCache = new HashMap<>(); public void getInfo(String key) { // uses cache Info info = myCache.get(key); if (info != null) return info; // if it's not in cache, then fetch it from the database info = Database.fetch(key); if (info == null) return null; // and store it in the cache myCache.put(key, info); return info; } 

Your cache grows and grows. And pretty soon the entire database gets sucked into memory. A better design uses an LRUMap (Only keeps recently used objects in cache).

Sure, you can make things a lot more complicated:

  • using ThreadLocal constructions.
  • adding more complex reference trees .
  • or leaks caused by 3rd party libraries .

What often happens:

If this Info object has references to other objects, which again have references to other objects. In a way you could also consider this to be some kind of memory leak, (caused by bad design).

Create a static Map and keep adding hard references to it. Those will never be GC’d.

 public class Leaker { private static final Map CACHE = new HashMap(); // Keep adding until failure. public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); } } 

You can create a moving memory leak by creating a new instance of a class in that class’s finalize method. Bonus points if the finalizer creates multiple instances. Here’s a simple program that leaks the entire heap in sometime between a few seconds and a few minutes depending on your heap size:

 class Leakee { public void check() { if (depth > 2) { Leaker.done(); } } private int depth; public Leakee(int d) { depth = d; } protected void finalize() { new Leakee(depth + 1).check(); new Leakee(depth + 1).check(); } } public class Leaker { private static boolean makeMore = true; public static void done() { makeMore = false; } public static void main(String[] args) throws InterruptedException { // make a bunch of them until the garbage collector gets active while (makeMore) { new Leakee(0).check(); } // sit back and watch the finalizers chew through memory while (true) { Thread.sleep(1000); System.out.println("memory=" + Runtime.getRuntime().freeMemory() + " / " + Runtime.getRuntime().totalMemory()); } } } 

Everyone always forgets the native code route. Here’s a simple formula for a leak:

  1. Declare native method.
  2. In native method, call malloc . Don’t call free .
  3. Call the native method.

Remember, memory allocations in native code come from the JVM heap.

I came across a more subtle kind of resource leak recently. We open resources via class loader’s getResourceAsStream and it happened that the input stream handles were not closed.

Uhm, you might say, what an idiot.

Well, what makes this interesting is: this way, you can leak heap memory of the underlying process, rather than from JVM’s heap.

All you need is a jar file with a file inside which will be referenced from Java code. The bigger the jar file, the quicker memory gets allocated.

You can easily create such a jar with the following class:

 import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class BigJarCreator { public static void main(String[] args) throws IOException { ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar"))); zos.putNextEntry(new ZipEntry("resource.txt")); zos.write("not too much in here".getBytes()); zos.closeEntry(); zos.putNextEntry(new ZipEntry("largeFile.out")); for (int i=0 ; i<10000000 ; i++) { zos.write((int) (Math.round(Math.random()*100)+20)); } zos.closeEntry(); zos.close(); } } 

Just paste into a file named BigJarCreator.java, compile and run it from command line:

 javac BigJarCreator.java java -cp . BigJarCreator 

Et voilà: you find a jar archive in your current working directory with two files inside.

Let's create a second class:

 public class MemLeak { public static void main(String[] args) throws InterruptedException { int ITERATIONS=100000; for (int i=0 ; i 

This class basically does nothing, but create unreferenced InputStream objects. Those objects will be garbage collected immediately and thus, do not contribute to heap size. It is important for our example to load an existing resource from a jar file, and size does matter here!

If you're doubtful, try to compile and start the class above, but make sure to chose a decent heap size (2 MB):

 javac MemLeak.java java -Xmx2m -classpath .:big.jar MemLeak 

You will not encounter an OOM error here, as no references are kept, the application will keep running no matter how large you chose ITERATIONS in the above example. The memory consumption of your process (visible in top (RES/RSS) or process explorer) grows unless the application gets to the wait command. In the setup above, it will allocate around 150 MB in memory.

If you want the application to play safe, close the input stream right where it's created:

 MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close(); 

and your process will not exceed 35 MB, independent of the iteration count.

Quite simple and surprising.

The interviewer was probably looking for a circular reference like the code below (which incidentally only leak memory in very old JVMs that used reference counting, which isn’t the case any more). But it’s a pretty vague question, so it’s a prime opportunity to show off your understanding of JVM memory management.

 class A { B bRef; } class B { A aRef; } public class Main { public static void main(String args[]) { A myA = new A(); B myB = new B(); myA.bRef = myB; myB.aRef = myA; myA=null; myB=null; /* at this point, there is no access to the myA and myB objects, */ /* even though both objects still have active references. */ } /* main */ } 

Then you can explain that with reference counting, the above code would leak memory. But most modern JVMs don’t use reference counting any longer, most use a sweep garbage collector, which will in fact collect this memory.

Next you might explain creating an Object that has an underlying native resource, like this:

 public class Main { public static void main(String args[]) { Socket s = new Socket(InetAddress.getByName("google.com"),80); s=null; /* at this point, because you didn't close the socket properly, */ /* you have a leak of a native descriptor, which uses memory. */ } } 

Then you can explain this is technically a memory leak, but really the leak is caused by native code in the JVM allocating underlying native resources, which weren’t freed by your Java code.

At the end of the day, with a modern JVM, you need to write some Java code that allocates a native resource outside the normal scope of the JVM’s awareness.

I don’t think anyone has said this yet: you can resurrect an object by overriding the finalize() method such that finalize() stores a reference of this somewhere. The garbage collector will only be called once on the object so after that the object will never destroyed.

As a lot of people have suggested, Resource Leaks are fairly easy to cause – like the JDBC examples. Actual Memory leaks are a bit harder – especially if you aren’t relying on broken bits of the JVM to do it for you…

The ideas of creating objects that have a very large footprint and then not being able to access them aren’t real memory leaks either. If nothing can access it then it will be garbage collected, and if something can access it then it’s not a leak…

One way that used to work though – and I don’t know if it still does – is to have a three-deep circular chain. As in Object A has a reference to Object B, Object B has a reference to Object C and Object C has a reference to Object A. The GC was clever enough to know that a two deep chain – as in A <--> B – can safely be collected if A and B aren’t accessible by anything else, but couldn’t handle the three-way chain…

Threads are not collected until they terminate. They serve as roots of garbage collection. They are one of the few objects that won’t be reclaimed simply by forgetting about them or clearing references to them.

Consider: the basic pattern to terminate a worker thread is to set some condition variable seen by the thread. The thread can check the variable periodically and use that as a signal to terminate. If the variable is not declared volatile , then the change to the variable might not be seen by the thread, so it won’t know to terminate. Or imagine if some threads want to update a shared object, but deadlock while trying to lock on it.

If you only have a handful of threads these bugs will probably be obvious because your program will stop working properly. If you have a thread pool that creates more threads as needed, then the obsolete/stuck threads might not be noticed, and will accumulate indefinitely, causing a memory leak. Threads are likely to use other data in your application, so will also prevent anything they directly reference from ever being collected.

As a toy example:

 static void leakMe(final Object object) { new Thread() { public void run() { Object o = object; for (;;) { try { sleep(Long.MAX_VALUE); } catch (InterruptedException e) {} } } }.start(); } 

Call System.gc() all you like, but the object passed to leakMe will never die.

(*edited*)

there are many different situations memory will leak. One i encountered, which expose a map that should not be exposed and used in other place.

 public class ServiceFactory { private Map services; private static ServiceFactory singleton; private ServiceFactory() { services = new HashMap(); } public static synchronized ServiceFactory getDefault() { if (singleton == null) { singleton = new ServiceFactory(); } return singleton; } public void addService(String name, Service serv) { services.put(name, serv); } public void removeService(String name) { services.remove(name); } public Service getService(String name, Service serv) { return services.get(name); } // the problematic api, which expose the map. //and user can do quite a lot of thing from this api. //for example, create service reference and forget to dispose or set it null //in all this is a dangerous api, and should not expose public Map getAllServices() { return services; } } // resource class is a heavy class class Service { } 

I think that a valid example could be using ThreadLocal variables in an environment where threads are pooled.

For instance, using ThreadLocal variables in Servlets to communicate with other web components, having the threads being created by the container and maintaining the idle ones in a pool. ThreadLocal variables, if not correctly cleaned up, will live there until, possibly, the same web component overwrites their values.

Of course, once identified, the problem can be solved easily.

The interviewer might have be looking for a circular reference solution:

  public static void main(String[] args) { while (true) { Element first = new Element(); first.next = new Element(); first.next.next = first; } } 

This is a classic problem with reference counting garbage collectors. You would then politely explain that JVMs use a much more sophisticated algorithm that doesn’t have this limitation.

-Wes Tarle