Los dos patrones principales "de fuga efectiva" en mi experiencia son:
- Estática y únicos que crecen gradualmente con el tiempo. Esto podría incluir cachés, conjuntos de conexiones mal implementados y usados, diccionarios de "cada usuario que hemos visto desde el inicio", etc.
- Las referencias de los objetos de larga duración a los objetos que fueron previstos tienen una vida corta. En C# esto puede suceder con eventos, y el patrón de observador equivalente podría dar el mismo tipo de efecto en Java. Básicamente, si le pide a un objeto (el observador) que mire otra (la fuente), generalmente termina con una referencia de fuente a observador. Eso puede terminar siendo la única referencia "en vivo", pero vivirá tanto tiempo como la fuente.
- Permgen pierde si sigue generando código nuevo dinámicamente. Estoy en un terreno más rocoso aquí, pero estoy bastante seguro de haber tenido problemas de esta manera. Es posible que esto se deba, en parte, a los errores de JRE que se han solucionado desde entonces: ha pasado demasiado tiempo desde que sucedió para que yo lo recuerde con certeza.
- Las pruebas unitarias que se mantienen en estado pueden durar más tiempo de lo que cabría esperar porque JUnit se aferrará a las instancias del caso de prueba. De nuevo, no puedo recordar los detalles, pero a veces esto hace que valga la pena tener una "anulación variable" explícita en el derribo, anacrónico como se ve.
No puedo decir que he encontrado regularmente fugas de memoria a ser un problema en Java (o .NET) sin embargo.
Oh, veo que esto sucede con bastante frecuencia si estás usando los cierres del pobre. No te estás dando cuenta de que estás aferrado al objeto exterior y todas sus referencias. Esto se ensucia * realmente * rápidamente. – krosenvold