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. Opus 和 AAC 声音编码格式

    Opus编码器 是一个有损声音编码的格式,由互联网工程任务组(IETF)近来开发,适用于网络上的实时声音传输,标准格式为RFC 6716.Opus 格式是一个开放格式,使用上没有任何专利或限制. Op ...

  2. 【Visual Studio】error C2220: 警告被视为错误 - 没有生成“object”文件 (转)

    原文转自 http://www.cnblogs.com/kex1n/archive/2011/10/19/2217266.html [错误原因] 该文件的代码页为英文,而我们系统中的代码页为中文. [ ...

  3. [Web Tools] 实用的Web开发工具

    模拟http请求:Postman https://www.getpostman.com 生成Json数据 http://www.json-generator.com/

  4. Codeforces 333E Summer Earnings(bitset)

    题目链接 Summer Earnings 类似MST_Kruskal的做法,连边后sort. 然后对于每条边,依次处理下来,当发现存在三角形时即停止.(具体细节见代码) 答案即为发现三角形时当前所在边 ...

  5. [原创][FPGA]有限状态机FSM学习笔记(一)

    1. 概述--何为有限状态机FSM? 有限状态机-Finite State Machine,简写为FSM,是表示有限个状态及在这些状态之间的转移和动作等行为的数学模型,在计算机领域有着广泛的应用.通常 ...

  6. 缓存区溢出检测工具BED

    缓存区溢出检测工具BED   缓存区溢出(Buffer Overflow)是一类常见的漏洞,广泛存在于各种操作系统和软件中.利用缓存区溢出漏洞进行攻击,会导致程序运行失败.系统崩溃.渗透测试人员利用这 ...

  7. spring与事务管理

    就我接触到的事务,使用最多的事务管理器是JDBC事务管理器.现在就记录下在spring中是如何使用JDBC事务管理器 1)在spring中配置事务管理器 <!-- JDBC事务 -->   ...

  8. redis 延迟消息

    1.查询下redis 是否打开了键空间通知功能 发现打开了,如果没有打开可以在执行下 我们可以看到参数设置 2.订阅下键空间或者事件通知 订阅键空间:subscribe __keyspace@0__: ...

  9. Maven添加坐标(依赖)及在Eclipse中的操作

    例如:添加一个spring-test.jar的依赖过程,普遍的做法就是直接操作pom.xml文件. 1.打开maven的中央仓库:http://search.maven.org/ 2.搜索仓库: 进入 ...

  10. 远程的jmeter自动执行完,如何回调通知被调用者“结束”状态

    场景:python应用通过paramiko在远程服务器上启动jmeter执行性能压测,压测完,jmeter通过回调函数告诉应用‘执行状态’ 方案:python应用写一个restful api,接收jm ...