Handling memory leaks in Java programs

Find out when memory leaks are a concern and how to prevent them

 
Published on February 01, 2001

How memory leaks manifest themselves in Java programs

Most programmers know that one of the beauties of using a programming language such as Java is that they no longer have to worry about allocating and freeing memory. You simply create objects, and Java takes care of removing them when they are no longer needed by the application through a mechanism known as garbage collection. This process means that Java has solved one of the nasty problems that plague other programming languages -- the dreaded memory leak. Or has it?

Before we get too deep into our discussion, let's begin by reviewing how garbage collection actually works. The job of the garbage collector is to find objects that are no longer needed by an application and to remove them when they can no longer be accessed or referenced. The garbage collector starts at the root nodes, classes that persist throughout the life of a Java application, and sweeps through all of the nodes that are referenced. As it traverses the nodes, it keeps track of which objects are actively being referenced. Any classes that are no longer being referenced are then eligible to be garbage collected. The memory resources used by these objects can be returned to the Java virtual machine (JVM) when the objects are deleted.

So it is true that Java code does not require the programmer to be responsible for memory management cleanup, and that it automatically garbage collects unused objects. However, the key point to remember is that an object is only counted as being unused when it is no longer referenced. Figure 1 illustrates this concept.

【many times member variables of a class that point to other classes simply need to be set to null at the appropriate time】

Figure 1. Unused but still referenced

The figure illustrates two classes that have different lifetimes during the execution of a Java application. Class A is instantiated first and exists for a long time or for the entire life of the program. At some point, class B is created, and class A adds a reference to this newly created class. Now let's suppose class Bis some user interface widget that is displayed and eventually dismissed by the user. Even though class B is no longer needed, if the reference that class A has to class B is not cleared, class B will continue to exist and to take up memory space even after the next garbage collection cycle is executed.

When are memory leaks a concern?

If your program is getting a java.lang.OutOfMemoryError after executing for a while, a memory leak is certainly a strong suspect. Beyond this obvious case, when should memory leaks become a concern? The perfectionist programmer would answer that all memory leaks need to be investigated and corrected. However, there are several other points to consider before jumping to this conclusion, including the lifetime of the program and the size of the leak.

Consider the possibility that the garbage collector may never even run during an application's lifetime. There is no guarantee as to when or if the JVM will invoke the garbage collector -- even if a program explicitly calls System.gc(). Typically, the garbage collector won't be automatically run until a program needs more memory than is currently available. At this point, the JVM will first attempt to make more memory available by invoking the garbage collector. If this attempt still doesn't free enough resources, then the JVM will obtain more memory from the operating system until it finally reaches the maximum allowed.

Take, for example, a small Java application that displays some simple user interface elements for configuration modifications and that has a memory leak. Chances are that the garbage collector will not even be invoked before the application closes, because the JVM will probably have plenty of memory to create all of the objects needed by the program with leftover memory to spare. So, in this case, even though some dead objects are taking up memory while the program is being executed, it really doesn't matter for all practical purposes.

If the Java code being developed is meant to run on a server 24 hours a day, then memory leaks become much more significant than in the case of our configuration utility. Even the smallest leak in some code that is meant to be continuously run will eventually result in the JVM exhausting all of the memory available.

And in the opposite case where a program is relatively short lived, memory limits can be reached by any Java code that allocates a large number of temporary objects (or a handful of objects that eat up large amounts of memory) that are not de-referenced when no longer needed.

One last consideration is that the memory leak isn't a concern at all. Java memory leaks should not be considered as dangerous as leaks that occur in other languages such as C++ where memory is lost and never returned to the operating system. In the case of Java applications, we have unneeded objects clinging to memory resources that have been given to the JVM by the operating system. So in theory, once the Java application and its JVM have been closed, all allocated memory will be returned to the operating system.

Determining if an application has memory leaks

To see if a Java application running on a Windows NT platform is leaking memory, you might be tempted to simply observe the memory settings in Task Manager as the application is run. However, after observing a few Java applications at work, you will find that they use a lot of memory compared to native applications. Some Java projects that I have worked on can start out using 10 to 20 MB of system memory. Compare this number to the native Windows Explorer program shipped with the operating system, which uses something on the order of 5 MB.

The other thing to note about Java application memory use is that the typical program running with the IBM JDK 1.1.8 JVM seems to keep gobbling up more and more system memory as it runs. The program never seems to return any memory back to the system until a very large amount of physical memory has been allocated to the application. Could these situations be signs of a memory leak?

To understand what is going on, we need to familiarize ourselves with how the JVM uses system memory for its heap. When running java.exe, you can use certain options to control the startup and maximum size of the garbage-collected heap (-ms and -mx, respectively). The Sun JDK 1.1.8 uses a default 1 MB startup setting and a 16 MB maximum setting. The IBM JDK 1.1.8 uses a default maximum setting of one-half the total physical memory size of the machine. These memory settings have a direct impact on what the JVM does when it runs out of memory. The JVM may continue growing the heap rather than wait for a garbage collection cycle to complete.

So for the purposes of finding and eventually eliminating a memory leak, we are going to need better tools than task monitoring utility programs. Memory debugging programs (see Related topics) can come in handy when you're trying to detect memory leaks. These programs typically give you information about the number of objects in the heap, the number of instances of each object, and the memory being using by the objects. In addition, they may also provide useful views showing each object's references and referrers so that you can track down the source of a memory leak.

Next, I will show how I detected and removed a memory leak using the JProbe debugger from Sitraka Software to give you some idea of how these tools can be deployed and the process required to successfully remove a leak.

A memory leak example

This example centers on a problem that manifested itself after a tester spent several hours working with a Java JDK 1.1.8 application that my department was developing for commercial release. The underlying code and packages to this Java application were developed by several different groups of programmers over time. The memory leaks that cropped up within the application were caused, I suspect, by programmers who did not truly understand the code that had been developed elsewhere.

The Java code in question allowed a user to create applications for a Palm personal digital assistant without having to write any Palm OS native code. By using a graphical interface, the user could create forms, populate them with controls, and then wire events from these controls to create the Palm application. The tester discovered that the Java application eventually ran out of memory as he created and deleted forms and controls over time. The developers hadn't detected the problem because their machines had more physical memory.

To investigate this problem, I used JProbe to determine what was going wrong. Even with the powerful tools and memory snapshots that JProbe provides, the investigation turned out to be a tedious, iterative process that involved first determining the cause of a given memory leak and then making code changes and verifying the results.

JProbe has several options to control what information is actually recorded during a debugging session. After some experimentation, I decided that the most efficient way to get the information I needed was to turn off the performance data collection and concentrate on the captured heap data. JProbe provides a view called the Runtime Heap Summary that shows the amount of heap memory in use over time as the Java application is running. It also provides a toolbar button to force the JVM to perform garbage collection when desired. This capability turned out to be very useful when trying to see if a given instance of a class would be garbage collected when it was no longer needed by the Java application. Figure 2 shows the amount of heap storage that is in use over time.

Figure 2. Runtime Heap Summary

In the Heap Usage Chart, the blue portion indicates the amount of heap space that has been allocated. After I started the Java program and it reached a stable point, I forced the garbage collector to run, which is indicated by the sudden drop in the blue curve before the green line (this line indicates a checkpoint was inserted). Next, I added, then deleted four forms and again invoked the garbage collector. The fact that the level blue area after the checkpoint is higher than the level blue area before the checkpoint tells us that a memory leak is likely, as the program has returned to its initial state of only having a single visible form. I confirmed the leak by looking at the Instance Summary, which indicates that the FormFrame class (which is the main UI class for the forms) has increased in count by four after the checkpoint.

Finding the cause

My first step in trying to isolate the problems reported by the tester was to come up with some simple, reproducible test cases. For this example, I found that simply adding a form, deleting the form, and then forcing a garbage collection resulted in many class instances associated with the deleted form to still be alive. This problem was apparent in the JProbe Instance Summary view, which counts the number of instances that are on the heap for each Java class.

To pinpoint the references that were keeping the garbage collector from properly doing its job, I used JProbe's Reference Graph, shown in Figure 3, to determine which classes were still referencing the now-deleted FormFrame class. This process turned out to be one of the trickiest aspects of debugging this problem as I discovered many different objects were still referencing the unused object. The trial-and-error process of figuring out which of these referrers was truly causing the problem was quite time consuming.

In this case, a root class (upper-left corner in red) is where the problem originated. The class that is highlighted in blue on the right is along the path that has been traced from the original FormFrame class.

Figure 3. Tracing a memory leak in a reference graph

For this specific example, it turned out that the primary culprit was a font manager class that contained a static hashtable. After tracing back through the list of referrers, I found that the root node was a static hashtable that stored the fonts in use for each form. The various forms could be zoomed in or out independently, so the hashtable contained a vector with all of the fonts for a given form. When the zoom view of the form was changed, the vector of fonts was fetched and the appropriate zoom factor was applied to the font sizes.

The problem with this font manager class was that while the code put the font vector into the hashtable when the form was created, no provision was ever made to remove the vector when the form was deleted. Therefore, this static hashtable, which essentially existed for the life of the application itself, was never removing the keys that referenced each form. Consequently, the form and all of its associated classes were left dangling in memory.

Applying a fix

The simple solution to this problem was to add a method to the font manager class that allowed the hashtable's remove() method to be called with the appropriate key when the form was deleted by the user. The removeKeyFromHashtables() method is shown below:

1
2
3
4
5
6
public void removeKeyFromHashtables(GraphCanvas graph) {
  if (graph != null) {
    viewFontTable.remove(graph);     // remove key from hashtable
                                     // to prevent memory leak
  }
}

Next, I added a call to this method to the FormFrame class. FormFrame uses Swing's internal frames to actually implement the form UI, so the call to the font manager was added to the method that is executed when an internal frame has completely closed, as shown here:

1
2
3
4
5
6
7
8
9
/**
* Invoked when a FormFrame is disposed. Clean out references to prevent
* memory leaks.
*/
public void internalFrameClosed(InternalFrameEvent e) {
  FontManager.get().removeKeyFromHashtables(canvas);
  canvas = null;
  setDesktopIcon(null);
}

After I made these code changes, I used the debugger to verify that the object count associated with the deleted form decreased when the same test case was executed.

Preventing memory leaks

You can prevent memory leaks by watching for some common problems. Collection classes, such as hashtables and vectors, are common places to find the cause of a memory leak. This is particularly true if the class has been declared static and exists for the life of the application.

Another common problem occurs when you register a class as an event listener without bothering to unregister when the class is no longer needed. Also, many times member variables of a class that point to other classes simply need to be set to null at the appropriate time.

Conclusion

Finding the cause of a memory leak can be a tedious process, not to mention one that will require special debugging tools. However, once you become familiar with the tools and the patterns to look for in tracing object references, you will be able to track down memory leaks. In addition, you'll gain some valuable skills that may not only save a programming project, but also provide insight as to what coding practices to avoid to prevent memory leaks in future projects.

Find out when memory leaks are a concern and how to prevent them的更多相关文章

  1. Identify Memory Leaks in Visual CPP Applications —— VLD内存泄漏检测工具

    原文地址:http://www.codeproject.com/Articles/1045847/Identify-Memory-Leaks-in-Visual-CPP-Applications 基于 ...

  2. 解决:Detected memory leaks

    最近在一个项目中,程序退出后都出现内存泄漏: Detected memory leaks!Dumping objects ->{171} normal block at 0x05785AD0, ...

  3. [Angular2 Router] Exiting an Angular 2 Route - How To Prevent Memory Leaks

    In this tutorial we are going to learn how we can accidentally creating memory leaks in our applicat ...

  4. The Introduction of Java Memory Leaks

    One of the most significant advantages of Java is its memory management. You simply create objects a ...

  5. 【转】简单内存泄漏检测方法 解决 Detected memory leaks! 问题

    我的环境是: XP SP2 . VS2003 最近在一个项目中,程序退出后都出现内存泄漏: Detected memory leaks! Dumping objects -> {98500} n ...

  6. On Memory Leaks in Java and in Android.

    from:http://chaosinmotion.com/blog/?p=696 Just because it's a garbage collected language doesn't mea ...

  7. _CrtSetBreakAlloc简单内存泄漏检测方法,解决Detected memory leaks!问题

    我的环境是: XP SP2 . VS2003 最近在一个项目中,程序退出后都出现内存泄漏: Detected memory leaks! Dumping objects -> {98500} n ...

  8. 内存泄露 Memory Leaks

    什么是内存泄露 内存管理一直是Java 所鼓吹的强大优点.开发者只需要简单地创建对象,而Java的垃圾收集器将会自动管理内存空间的分配和释放. 但在很多情况下,事情并不那么简单,在 Java程序中总是 ...

  9. Activitys, Threads, & Memory Leaks

    Activitys, Threads, & Memory Leaks 在Android编程中,一个公认的难题是在Activity的生命周期如何协调长期运行的任务和避免有可能出现的内存泄漏问题. ...

随机推荐

  1. NGINX白名单功能,ngx_http_limit_conn_module和ngx_http_limit_req_module值设置多少才合适呀?

    要根据不同的应用慢慢学习测试? 我现在设置的10左右,看看再说吧... #增加限制规则,如果不能正常访问,则需要调节这两个值 -- #增加ip白名单功能 geo $whiteiplist { defa ...

  2. JS-禁止移动端长按页面后弹出“在浏览器打开”这个菜单

    阻止浏览器的touchstart事件 document.addEventListener('touchstart', function(e) {        e.preventDefault(); ...

  3. python笔记2:函数

    5. 函数 Python函数代码块以 def 关键词开头; 函数内容以冒号起始,并且缩进. *注: python中,strings, tuples, 和 numbers 是不可更改的对象,而 list ...

  4. js 克隆数据 (数组的深浅拷贝)

    var a1 = [1,2,3]; var a2 = a1; a2[0] = 90; console.log(a1[0]) //90 解析:数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结 ...

  5. 根据ipnut的maxlength实时提示输入的字符长度

    $(function(){ $("body").on("focus","input,textarea", function() { if(! ...

  6. SpringBoot中如何上传Upload

    [学习笔记] 5.上传:马克-to-win@马克java社区:根据第3部分的helloworld例子,用那个项目做底子.pom.xml都不用改变.参考项目bootUpload1.static/inde ...

  7. Java判断语句中判断条件的执行顺序

    if判断里面如果是多个条件的判断,会按照从前往后的顺序执行.基本所有语言都是一致的设计,这样做能提高程序的效率. 比如: if ( conditionA && conditionB ) ...

  8. Css Position定位(简易版本)

    准备前的知识: 定位只对块级起作用.如div,p等元素是块级元素,如果是内联元素则可以先变成块级元素,display:block即可. 开始讲解: 定位共四种:static,fixed,relativ ...

  9. Android onCreate 的savedInstanceState 作用

    在activity的生命周期中,只要离开了可见阶段,或者说失去了焦点,activity就很可能被进程终止了!,被KILL掉了,,这时候,就需要有种机制,能保存当时的状态,这就是savedInstanc ...

  10. cocos2d-x3.0 PageView

    .h加入例如以下代码: void pageViewEvent(Ref *pSender, PageViewEventType type); .m layout = Layout::create(); ...