Tracking Down Memory Leaks in Java
June 14, 2013I recently finished programming my side-scroller game "Fluffy Chicken Adventures" and launched it on my website. You can play it here! Oddly, I found some problems occurred while running the game on the website that did not occur when I tested the game on my computer. I found that the game would always crash before I could complete all the levels. The applet suddenly just froze and stopped working altogether. The Java Console also disappeared immediately when this happened so I could not see if there were any error messages. It was as if System.exit(0) was called.
At first I could not figure out what caused this strange problem. After some research, however, I found out that a JavaOutOfMemory error could cause this. Monitoring the memory used by the java process in task manager revealed that the game always seemed to crash when the memory used went slightly above 60 MB. I read online that on most computers applets are only allocated between 60 MB - 90 MB. I realized that I did not encounter any crashes with Eclipse because it must allocate more memory than to applets than is allocated to Java applets embedded in webpages.
I wondered if my game simply needed more memory, or if there was some kind of memory leak in my program. The crash typically only occurred near the last level of the game. It seemed like the memory needed to load each level remained in use. But the JVM garbage collector is supposed to remove objects that are no longer referenced. I noticed I could make the game crash even by staying on the first level. It seemed standing still on the first level while nothing was happening did not increase the memory used by the applet. However, other characters firing lasers did seem to increase the memory. This led me to believe that the Linked List of active lasers the game kept was the cause of the problem. However, in the code I clearly removed all the unused projectiles. After all, the garbage collector should clean up all of the old projectiles when it loads a new stage and the old one becomes unreferenced.
I could not understand why the problem was happening but I was quite sure I had some kind of memory leak on my hands. I have never had to debug memory leaks before so I was unsure where to start. After reading about getting heap dumps from the JVM and other memory analysis tools I found out about a tool called jconsole.exe that comes with the JDK. It was this tool that actually allowed me to find the problem in my code.I will explain how to use JConsole. Firstly you should have a recent version of the JDK. I assume you probably already have that if you are developing Java applications. You can find "jconsole.exe" in the bin folder of where you installed the JDK. You can see where it is on my computer in the picture below. It will probably be in a similar spot on your computer.
Before you run jconsole.exe, make sure the java application you are interested in is running. Now open jconsole.exe. You will see a list of running Java processes on your computer. Hopefully the one you want to debug is there, select it. You can see what the screen looks like in the picture to the left. I was running my applet in Eclipse's applet viewer.If JConsole connects to the application properly you will then see a screen displaying information about the memory, CPU, and thread usage of the program. It was the "threads" tab that showed me the problem with my application. At the time I took the screenshot (shown below) it had created 738 threads! Why would there be so many threads? It turned out that my program had created hundreds of "Direct Clip" threads to play sounds, and the threads just wait around after they play the sounds. It actually created a new one each time a sound effect was played. All of these threads take a small amount of memory. Eventually it has thousands of threads and crashes. I believe there is also a limit to how many threads a Java can create. Apparently the JVM garbage collector does not cleanup old idle threads created by the Java Sound "Clip" objects automatically. The clips must be explicitly terminated using the close() function. Amazingly when I toggle sound off in the "Settings" menu of my game no direct clip threads are created and the game does not crash!
The way I was handling playing sound effects in my game was quite foolish, and I have been able to fix it. It is interesting how I was misled about the source of the problem. I thought it was the projectile objects themselves, or perhaps the images. I never thought about the sound effects.There are a few points you should take away from this article. Firstly, memory leaks are possible in Java, just not in the same way they occur in C/C++. Also, memory tracking tools like JConsole are very useful and can save you a great deal of time. These memory issues are very difficult to track, especially when you are dealing with a larger project, like my game. Moreover, there are quite a few different types of problems you can detect with memory tools beyond what I did for this project. Finally, it is worth using such tools on for your project during development. I wrote the SoundEffect class for my game about a month ago, and had to idea that it wasted so much memory. Had I used JConsole earlier, it would have been obvious.
Note: On further reflection, I believe I actually was experiencing a "Too Many Threads" exception. Rather than crashing due to lack of memory, the program likely exceeded the number of threads the Java virtual machine allows a process to create. Therefore this may not have been a "memory leak". However, it has similar properties and does involve resources not being closed or cleaned up properly.
Return to Blog