Finding Memory Leaks Using the NetBeans Profiler

Gregg Sporar, Sang Shin


 

The NetBeans profiler has powerful capabilities to help you track down memory leaks in Java applications.  Using instrumentation, you see allocations on the heap happen as your program runs and the profiler provides statistical values to highlight patterns in your application's memory allocations.  This "behavioral" approach can help you quickly identify the most likely memory leak candidates, even in situations where each individual leak is very small.  The profiler also has a HeapWalker that can be used to examine the relationships between objects on the heap. This "after the fact" approach can help you quickly identify why a particular object is not being garbage collected by the Java virtual machine. And with its tight integration into the developer work flow, the profiler makes it easy to start/stop profiling sessions and more importantly, to go from profiler results directly into the source code that has the problem.

In this hands-on lab, you are going to use the NetBeans profiler to find out where exactly a memory leaking code is located, first in a web application (Exercise 1) and then in a stand-alone application (Exercise 2).  The latter is based on a real-life use case where the NetBeans profiler was used to find a memory leak.   In Exercise 3, you are going to use the HeapWalker feature of the profiler to analyze a heap.  In Exercise 4, you will learn how to attach the NetBeans profiler to an already-running application.


Expected duration: 100 minutes (excluding homework)




Software Needed

Before you begin, you need to install the following software on your computer. 

Operating Systems/platforms you can use


Web/App servers you can use NetBeans profiler


The NetBeans profiling tools can be used with *any* web/application server that is running on JDK 5 update 4 or higher. Please note that the ease-of-use varies quite a bit depending upon which web/application server you are using.  This is because the NetBeans plugins that provide support for web/application servers in the IDE vary in their support of the profiling tools.  The web/appplication servers that work the best with the NetBeans profiler include (1) GlassFish (2) JBostt (3) Tomcat.  On all three of these, you can literally start a profiling session with a single mouse click, assuming that you have registered the server with the IDE.

For other web/application servers (and really, for any other Java application that your run outside the IDE), it gets a bit more complicated.  NetBeans has an "attach" wizard that will step you through what you need to do to modify the startup script for your web/application server so that it can be profiled.  Specific instructions are even included for popular servers such as Weblogic, etc.

Change Log


Things to do (by Authors)


Lab Exercises


Exercise 0: Perform JDK calibration


In this exercise, you will perform calibration against the JDK running on your system.  The calibration is required in order to perform profiling. It does not affect in any way the behavior of your Java applications.  This needs to be done only once.

Note: Instrumenting the bytecode of the profiled application imposes some overhead. To guarantee the high accuracy of profiling results, NetBeans Profiler needs to collect calibration data in order to "factor out" the time spent in code instrumentation. You need to run the calibration process for each JDK you will use for profiling. The calibration data for each JDK is saved in the .nbprofile directory in your home directory. You are prompted to run the calibration the first time you invoke NetBeans Profiler. You are also prompted if the calibration data for the local machine and JVM is unavailable.

(0.1) Run profiler calibration


1. Select Profile from top-level menu and select Advanced Commands->Run Profiler Calibration.  (Figure-0.20 below)


Figure-0.20: Run Profiler Calibration

Figure-0.21: Select Java Platform to calibrate

Figure-0.22: Information dialog box


Exercise 1: Build and run "memoryleak" Web sample application in Memory Profiling mode

In this exercise, you are going to profile the memoryleak sample application that contains a badly designed code fragment, in which double and float primitives are allocated in a loop and then saved as an entry into a HashMap, which would result in OutOfMemoryError eventually. The goal of using the profiler is to find out where this code fragment is located.

Learning points:

  1. Open "memoryleak" sample project
  2. Run the project in profiling mode
  3. Modify "memoryleak" sample project with a custom class

(1.1) Open "memoryleak" sample project

0. Start NetBeans IDE (if you have not done so yet). 
1. Open memoryleak NetBeans project. 

2. Observe the memory leaking code fragment.  The goal of this exercise is to find this memory leaking code using the NetBeans profiler.


Figure-1.11: Code fragment that contains the memory leaking code

                                                                                                                       return to top of the exercise



(1.2) Run the project in "profiling" mode



1. Right click memoryleak project node and select Profile. (Figure-1.20 below)


Figure-1.20: Profile Main Project


If you see the following dialog box, choose JDK 1.6 (Default) as shown below and click OK button.




Trouble-shooting:  If you see the following dialog box, it is because the JDK that is being used by the NetBeans profiler has not been calibrated.
Solution: Perform the calibration as described above.





2. If the project is being profiled for the first time, you may encounter this dialog box. Click OK. (Figure-1.21 below)  If you have profiled the project previously, you will not see this dialog box.


Figure-1.21: Permission to Profile

3. Configure Analyze Memory options.

Figure-1.22: Analyze Memory options

4. Trigger the memory-leaking.

Figure-1.23: Run the browser

Trouble-shooting: If you are experiencing a problem running this application over Tomcat, it is highly likely due to the fact that you have not installed Tomcat.  Change the deployment platform to GlassFish V2 by (1) Right click memoryleak project node and select Properties (2) In the Project Properties dialog box, select Run on the left and and select GlassFish V2 from the Server drop-down menu.

5. Display the live profiling result.

Figure-1.24: Profiling result

<Learning point: Columns of Live Results> The columns of the live results provide object allocation and memory usage information.
<Learning point: Surviving Generations> Surviving Generations metrics cannot be easily defined with just one line, so let's make a three-line definition: (This is quoted from Uncovering Memory leaks Using NetBeans Profiler article by Jin Sedlacek.)

Typically there are several long-lived objects (like an application's main JFrame etc.) in an application. The generation's age increases during the application's run time but still represents one or a few Surviving Generations.

Most applications also have short-lived objects which are created very frequently (such as Dimension etc.) but these objects are released very soon, typically within only a few garbage collections. This means that they represent only a few Surviving Generations (with generation's age of 1, 2, 3 etc.).

If we merge the two above cases, the total number of Surviving Generations in typical applications is quite stable. You may have long-lived objects representing, for example, 3 Surviving Generations, and short-lived objects representing, for example, 5 Surviving Generations, which means there will be about 8 Surviving Generations during the application's runtime.

Of course, in some application, for example a Swing application, dialogs and other components are created during run time and as a result the number of Surviving Generations grows a bit. But after some period of time the number of Surviving Generations should become stable because all long-lived objects have already been created and newly-created short-lived objects are periodically being released from the heap.

However, if there is a memory leak in an application which prevents newly-created objects from being released from the heap (for example objects stored in a Collection and never removed), the number of Surviving Generations grows. Why? Because between every two garbage collections a new generation (of leaking objects) is created and it survives each successive garbage collection. During run time the number of Surviving Generations whose generation's age differ by one unit increases, and that's exactly what the Surviving Generations metrics is able to detect regardless of how much memory is wasted by such a leak. That's why using the Surviving Generations metrics can help you discover a memory leak much sooner, before it consumes the entire heap and causes an OutOfMemoryError.



6. Detect which object types are not being garbarge-collected.

Figure-1.25: Take the Snapshot

7. Find the location of the code fragment that leaks memory.


Figure-1.27: Code is found

8. Stop profiling.
Note: The memoryleak project is configured to run over Tomcat as a deployment platform.  You can also perform the profiling over GlassFish v2 as well.
                                                                                                                       return to top of the exercise


(1.3) Modify "memoryleak" sample application with a custom class


In this step, you are going to create a Person class whose array will be used as an object that will be saved into a Hashmap.

1. Create a Person class.

Figure-1.31: Create a new class

2. Modify the IDE generated Person.java with the code in Code-1.32 and in Figure-1.33 below.

package demo.memoryleak;

public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
Code-1.32: Person.java


Figure-1.33: Person.java

3. Modify the LeakThread.java as shown in Code-1.34 and Figure-1.35 below. The code fragments that need to be modified are highlighted in bold font.

package demo.memoryleak;

public class LeakThread extends Thread {
    private java.util.HashMap<float[], Person[]> map =
                new java.util.HashMap<float[], Person[]> ();
    public boolean stop = false;
    public void run () {
        stop = false;
       
        //////////////////////////////////////////////
        while (!stop) {
            try {
                map.put (new float[1], new Person[1]);
                Thread.sleep (100);
                System.gc ();
            } catch (InterruptedException e) {
                return;
            }
        }
        //////////////////////////////////////////////
    }
}
Code-1.34: Modified LeakThread.java


Figure-1.35: Modified LeakThread.java

4. Run the application in profiling mode.

Figure-1.36: Run the application in profiling mode.

5. Start the leaking.
6. Display the profiling result.

Figure-1.37: Generation count of demo.memoryleark.Person[] keeps increasing

Figure-1.38: Go to Source

Figure-1.39: Source code

7. Stop profiling.
                                                                                                                        return to top of the exercise

Summary

In this exercise, you learned how to use the profiler to monitor the memory usage of your application. You learned how to interpret the data collected, especially Generation, which can be used to find out which objects are not being garbage-collected.

                                                                                                                        return to the top




Exercise 2: Build and run "HttpUnit" Java SE sample application in Memory profiling mode

This exercise is based on an actual usage of the NetBeans profiler as part of the development and testing of a production application.  Andrés González of Spain used the NetBeans profiler to track down a memory leak in HttpUnit.  HttpUnit is an open source project that provides a framework for unit testing of web pages. It essentially acts as a web browser so that you can write unit tests to verify that the correct pages are being sent back from your web application.

Andrés was using HttpUnit to run multi-day tests of his application. He discovered something sort of odd - the JVM that was running the tests he created with HttpUnit would eventually report an OutOfMemoryError. The tests had to run for long period of time before the OutOfMemoryError would occur though, so apparently each memory leak was relatively small.

Andrés used the NetBeans profiler to track down the problem and he wrote a blog entry about it (the entry is in Spanish, but Google can translate). There is also this thread out on the HttpUnit mailing lists where he reported what he found.

The HttpUnit sample application included here is not the program that Andrés wrote - it does, however, encounter the same problem in HttpUnit that Andrés has used. The underlying issue has to do with the way that HttpUnit processes web pages that include JavaScript. The framework does have support for a subset of JavaScript, but not the entire language. If it encounters JavaScript that it does not understand it will throw an exception. If the web page you are attempting to test includes JavaScript that HttpUnit does not support and you do not want to wade through all those exceptions in the output then the HttpUnit documentation recommends that your test program call HttpUnitOptions.setExceptionsThrownOnScriptError( false );

The side effect, however, is that HttpUnit will store every exception thrown during its JavaScript processing in an ArrayList and it never removes them. So if your tests access enough web pages that either have JavaScript errors or that include JavaScript that HttpUnit does not support, you can eventually get an OutOfMemoryError.

One additional note on the sample application - it does not require a web server in order to run. HttpUnit has a nice feature where if what you want to test is the response from a servlet then it can host the servlet for you, in the same JVM as your test application. So the sample application consists of:

The process of finding out the offending code fragment is similar to what you've done in Exercise 1.
  1. Open "HttpUnit" sample application
  2. Run the application in "profiling" mode
  3. Display and analyze the collected data
  4. Correct the problem

(2.1) Open "HttpUnit" sample application

Before you start this exercise, it is highly recommended for you to read the above and have a good understanding of the context.

1. Open HttpUnit NetBeans project. 

                                                                                                                       return to top of the exercise


(2.2) Run the application in "profiling" mode


1. Make sure the HttpUnit project is the Main project.  (The project node should be in bold-font.)  If it is not the Main project, right click the project and select Set as Main Project.

2. Select Profile from the top-level menu and select Profile Main Project.
3. Configure Analyze Memory options and run profiling.

Figure-2.20: Configure Analyze Memory options
                                                                                                                       return to top of the exercise


(2.3) Display and analyze the collected data


1. Display Telemetry Overview.

Figure-2.21: Application is running

Figure-2.22: VM Telemetry Overview

In the graph on the left, the red shading indicates the allocated size of the JVM heap. The purple overlay indicates the amount of heap space actually in use. In the example above, the allocated heap size at the last update was about 5 megabytes. Of that, a little over 2 megabytes is actually being used to hold Java objects.

The graph on the right shows the count of active threads in the JVM.

The graph in the center shows two important heap statistics.

2. Display live profiling result.

Figure-2.23: Live Profiling Results

3. Detect which object types are not being garbarge-collected.

Figure-2.25: Take Snapshot and Show Allocation Stack Traces

4. Stop the profiling

Figure-2.26: Stop the profiling

5. Analyze the profiled data.

Note: The displayed view shows all the places in the application where Strings were allocated. In a typical Java application, there can be dozens or even hundreds or thousands of places where Strings are allocated. What you want to know is: which of those allocations are resulting in memory leaks? You can use the generation count as a key indicator. Notice that only one of the allocation locations in this group has created Strings that have a large value for Generation count: java.lang.StringBuffer.toString() - it has the value of 20 in the Figure-2.28 below.  If we were to continue running the application and take more snapshots we would see larger values each time.

Now that you know that StringBuffer's toString() method is allocating Strings that appear to be candidate memory leaks, you need to determine how that relates to our application's usage of HttpUnit.

Figure-2.28: Expand the suspicious code

Figure-2.29: Display the source of the main() method

Figure-2.30: Sample application's call to the code that has a memory leak

6. Take a look at the memory-leaking code.

Figure-2.31: See the source code that

Figure-2.32: Memory leaking code

                                                                                                                       return to top of the exercise


(2.4) Correct the problem


In this step, you are going to correct the problem - actually removing the offending code for the sake of the simplicity of the exercise.

1. Comment out the offending code in the com.meterware.httpunit.javascript.JavaScript$JavaScriptEngine.handleScriptException() method.

        private void handleScriptException( Exception e, String badScript ) {
            final String errorMessage = badScript + " failed: " + e;
            if (!(e instanceof EcmaError) && !(e instanceof EvaluatorException)) {
                e.printStackTrace();
                throw new RuntimeException( errorMessage );
            } else if (isThrowExceptionsOnError()) {
                e.printStackTrace();
                throw new ScriptException( errorMessage );
            } else {
                // _errorMessages.add( errorMessage ); // Commented out
            }
        }
Code-2.33: Remove the offending code

2. Rerun the application in the profiling mode.
3. Observe that the Generation number of the String class remains in the range of 10 to 12.  (Figure-2.34 below)  This indicates that the memory leaking problem has been resolved.


Figure-2.34: String objects are garbage collected.

                                                                                                                        return to top of the exercise

Summary

In this exercise, you have used NetBeans profiler to find the memory leaking code in the production application - HttpUnit code.  By the way, the HttpUnit provides a method that will clear the ArrayList in which these Strings are being collected.

                                                                                                                        return to the top


Exercise 3:  Use "HeapWalker" to analyze memory problem


The HeapWalker provides a complete picture of the objects on the heap and the references between the objects.  The HeapWalker is especially useful for analyzing binary heap dump files produced when an OutOfMemoryError occurs.  The Find Nearest GC Root feature can help you track down memory leaks by showing the owner of the reference that prevents an object from being garbage collected.  For more detailed information on NetBeans HeapWalker, please see "Using HeapWalker" online document.
  1. Open "PrimeNumber" project
  2. Run "PrimeNumber" project in "profiling" mode
  3. Use HeapWalker to analyze the problem
  4. Take and load Heap Dump


(3.1) Open "PrimeNumber" project

1. Open PrimeNumber NetBeans project. 

2. Observe that the maximum heap size is set to 6M bytes.  This is to use smaller heap space than the default (in order to generate the problem fast.)

Figure-3.10: Maximum heap size

                                                                                                                       return to top of the exercise


(3.2) Run "PrimeNumber" project in profiling mode


1. Make sure the PrimeNumber project is the Main project.  (The project node should be in bold-font.)  If it is not the Main project, right click the project and select Set as Main Project.
2. Select Profile from the top-level menu and select Profile Main Project.
3. Configure profiling mode.

Figure-3.20: Monitor application

4. Run the application.

Figure-3.21: Calculate Prime Number under 1000000

Figure-3.22: Result

5. Generate OutOfMemoryError condition.

Figure-3.23: Question

Figure-3.24: java.lang.OutOfMemoryError:  Java heap space

                                                                                                                       return to top of the exercise


(3.3) Use HeapWalker to analyze the problem


1. See the Summary view.

Figure-3.30: Summary view of the HeapWalker

2. View the Classes  view.

Figure-3.31: Classes view

3. Display the instances.

Figure-3.32: Show in Instances View of int[] array

Figure-3.33: Display information on int[] instance that holds large memory

4. Display the contents of the array instance.

Figure-3.35: View the values of the <items 0-499>

Figure-3.36: Observe the int[] array contains the prime numbers

We have found something on the heap that should not be there - in this case, this appears to be an array that was used during the calculation that should have been garbage collected after the calculation. So why didn't the JVM's garbage collector remove it? There must be some accidental reference to it that is being made (and that should be cleared). This is where the References information comes in handy.

5. Display reference information.

Figure-3.37: Show Nearest GC Root

Figure-3.39: Go To Source

Figure-3.40: Find Usage of the completeResults_

Figure-3.41: Find Usage

Figure-3.42: Result of the Find

Figure-3.43: Code fragment

                                                                                                                       return to top of the exercise


(3.4) Take and load Heap Dump


One last thing to note - the profiler's heapwalker can also be used on any binary heap dump file produced by one of Sun's JVMs. There are a variety of ways to get a JVM to produce a heap dump; as an example, there is a command line flag (-XX:-HeapDumpOnOutOfMemoryError ) that you can use to have the JVM create the file whenever an OutOfMemoryError is thrown. You can then open that file with this feature.

You can also create a Heap Dump from the IDE.

1. Create a Heap Dump.

Figure-3.46: Take Heap Dump

Figure-3.47: Specify the directory

Figure-3.48: Heap Dump Saved.

2.  Load the Heap Dump file.

Figure-3.49: Load Heap Dump

Figure-3.50: Loaded heap dump

                                                                                                                        return to top of the exercise

Summary

In this exercise,  you have learned how to use the HeapWalker of the NetBeans profiler to analyze a memory usage problem, specifically to find a reference to an object that prevents it from being garbage collected.

                                                                                                                        return to the top


Exercise 4: Perform memory usage analysis on locally running applications


With JDK 6 you can attach the profiler to an application that is already running. No special JVM command line flags are necessary when you start that application.  Dynamic attach works even if you do not have a NetBeans project defined for the application.

Please note that Dynamic attach only works if the application being profiled is running on JDK6 and the NetBeans IDE itself is running on JDK6.  JDK5 does support only direct attach but not support dynamic attach.

In other words, "Dynamic" profiling does not require any command line flags when you start the JVM for the application.  Note this only works on JDK 6.  The "Direct" profiling is just the regular old way of starting a profiling session - which works with JDK 5 and higher.  In other words, the JVM for the application is started with the command line flags that tell it to wait for the profiling tool to attach, etc.

  1. Create and run AnagramGame project
  2. Dynamicaly attach profiler to locally running (but external to NetBeans IDE) JDK6-based Java application
  3. Locally attach profiler to locally running (but external to NetBeans IDE) JDK5-based Java application


(4.1) Create and run AnagramGame project


1. Create a new NetBeans project.


Figure-4.10: Create a new project


Figure-4.11: Select Anagram Game sample application


Figure-4.12: Finish creating the project

2. Make sure the project is using JDK 6.


Figure-4.13: Make sure you are using JDK 6.

3. Build the project.


Figure-4.14: Take the command line


Figure-4.15: Command line

4. Run the program external to the NetBeans IDE.

Note: If you are running on FAT32 system on Windows, open a terminal window, and type java -XX:+PerfBypassFileSystemCheck -jar "C:\myprojects\AnagramGame\dist\anagrams.jar".

Note: If you are running the application over JDK 5, type java -Dcom.sun.management.jmxremote -jar "C:\myprojects\AnagramGame\dist\anagrams.jar"


Figure-4.16: Run the programming outside of the IDE


Figure-4.17: Anagram program

                                                                                                                       return to top of the exercise


(4.2) Attach NetBeans profiler dynamically to the locally running (but externally from NetBeans IDE) application


1. Select Profile->Attach Profiler. (Figure-4.20 below)


Figure-4.20:  Attach profiler

2. Configure Memory Analyzer options.

Figure-4.21: Attach Profiler

3. Configure Attach options.

Figure-4.23: Select Target Type

Figure-4.24: Review Attach Settings

Figure-4.25: Manual Integration

4. Select the target process to monitor.

Figure-4.26: Select the target process


Trouble-shooting: If you experience <Error Getting Running Processes> as shown below, it is highly likely because you are running the AnagramGame application on a Windows system that has FAT32 file system.  This symptom is explained in the "hsperfdata is not being created on non-NTFS partitions".
Workaround:  When run the AnagramGame application in a terminal window, type java -XX:+PerfBypassFileSystemCheck -jar "C:\myprojects\AnagramGame\dist\anagrams.jar" not java -jar "C:\myprojects\AnagramGame\dist\anagrams.jar".


Figure-4.27: Process is not being shown


5. Analyze the process.

Figure-4.26: Display live results


                                                                                                                        return to top of the exercise


(4.3) Attach NetBeans profile directly r to the locally running (but externally from NetBeans IDE) application


1. Stop anagram application.
2. Start the Anagram (or any other application you want to profile) with the flag.
Example (for starting Anagram application):  java  -agentpath:"C:\Program Files\NetBeans 6.0.1\profiler2\lib\deployed\jdk15\windows\profilerinterface.dll=\"C:\Program Files\NetBeans 6.0.1\profiler2\lib\"",5140 -jar "C:\myprojects\AnagramGame\dist\anagrams.jar" (Figure-4.31 below)

Profiler Agent: Initializing...
Profiler Agent: Options: >"C:\Program Files\NetBeans 6.0.1\profiler2\lib",5140<
Profiler Agent: Initialized succesfully
Profiler Agent: Waiting for connection on port 5140 (Protocol version: 8)


Figure-4.31: Start the java application with a flag

3. Attach the profiler.

Figure-4.32: Attach Profiler

Figure-4.33: Select Local and Direct attachment options

Figure-4.34: Review Attach Settings

Figure-4.35: Read information on Manual integration

Figure-4.36: Perform attachment

Figure-4.37: Profiler agent

4. Observe the profiling result.

Figure-4.38: Profiling result

5. Close profiling session.

Figure-4.39: Finish the profiling

Figure-4.40: Connection is closed

Summary

In this exercise,  you have learned how to dynamically and locally attach the NetBeans profiler to a locally running application.

                                                                                                                        return to the top


Exercise 5: Perform dynamic attachment to JDK6 running App server


<tbd>
  1. Create and run AnagramGame project
  2. Dynamicaly attach profiler to locally running (but external to NetBeans IDE) JDK6-based Java application
  3. Locally attach profiler to locally running (but external to NetBeans IDE) JDK5-based Java application

(5.1) Create and run AnagramGame project


1.


Figure


Figure


Figure


Figure


Figure


Figure


Figure


Figure

Step 1: Original files C:\Program Files\glassfish-v2ur1\config\asenv.bat and C:\Program Files\glassfish-v2ur1\domains\domain1\config\domain.xml will be backed up to asenv.bat.backup and domain.xml.backup

Step 2: The following line will be modified in asenv.bat:
set AS_JAVA="C:\Program Files\Java\jdk1.6.0_04"

Step 3: If the domain.xml config file contains <profiler> section, this section will be removed.



Figure






Homework Exercise (for people who are taking Sang Shin's "Advanced Java Programming online course")


<to be provided>