Case Study of NetBeans Profiler


1.Introduction

NetBeans profiler is based on the profiling technology known as Jfluid that Sun has developed. I like Jfluid. It helped me detect memory leak and performance bottleneck in several Java applications. The Jfluid technology is being integrated into NetBeans. Now, we can try it with NetBeans 4.0. In this article, I use Profiler Milestone 3 build. I would like to share my experience how it helps developers improve their applications. I can only show you the door. You have to walk through it.

If you are new to NetBeans Profiler, please first visit the following URL. There are several documents for setting up Profiler.


2. Method of detecting memory leak issues

Here are the principles of detecting memory leak issues with Profiler. Detecting a memory leak is done by the following steps.

  • Monitor JVM heap size and its trend.
  • If the used heap size continues to increase, check which object occupies most of JVM heap.
  • Look at how/where the object is allocated and released.
  • Check if the object is allocated and released appropriately in the inline code.

How do I complete the above steps with Profiler? We can do by looking at the several graphs of Profiler.

Heap graph in 'Profiler' window

First, let's look at the 'Profiler' window that is located at the bottom of Netbeans windows. The left figure is the graph of Heap size and used heap. Then you can see how heap size and used heap have been changed through your application execution. If used heap is increasing over time, it has possibly a memory leak problem.

Memory leak detection

The figure above briefly explains how JVM uses memory. In this case, used heap in the left graph is relatively stable. Because it does not keep growing over time.

Live Objects Memory graph

Next, let's look at the memory graph to see which object is occupying the most of memory. In 'Live Bytes' field, the graph shows the memory size used by each objects. 'Live Objects' field represents the number of objects which are in JVM heap. According to them, We can see which object occupies memory. Please also look at the 'Generations' field. 'Generations' represents number of survived generations. If objects of a class continue to be allocated over time and are not released, the number of 'Generations' will keep growing. This is a typical memory leak situation, and the field will tell you that.

In above 'Live Objects Memory graph' example, HashMap$Entry occupies more area of memory. However, the number in 'Generations' field is low. It's hard to conclude if there is a memory leak. We need to run the application longer time and monitor the memory usage.

Reverse call graph

If there is any suspicious object, then, let's look at the reverse call graph. It helps us to see where the objects that are occupying memory are allocated. It will give us ideas what we should look at in our java code.

The above figure shows a reverse call graph of 'char' object. The graph indicates that 'char' is allocated in StringBuffer.expandCapacity(int) method. By reverse call graph, we can specify where the object in question is allocated. Then we look at the inline code at the place.


3.Case Study: Detecting A Memory Leak

Next, let me introduce a case study of detecting a memory leak problem with Profiler. NetBeans Profiler has the functionality of object liveness profiling (Record both object creation and garbage collection). It will show you the statistics of allocating and deallocating objects to help you detect any memory issue.

Sample code

To demonstrate detecting a memory leak with Profiler, I use the following sample code.

[TestHash Sample Code]


/*
 * Main.java
 *
 * Created on 2005/01/06, 11:00
 */

package memoryleak;

import java.util.*;
import java.io.*;

/**
 *
 * @author root
 */
public class Main {
    Hashtable hashtable = new Hashtable();
    Square sq[] = new Square[10];
   
    /** Creates a new instance of Main */
    public Main() {        
        
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        Main testhash = new Main();   
        for(int i=0; i<100000; i++) {
            testhash.create(i);  
            testhash.use(i);    
            testhash.release();
        }
    }
    
    // Create Square objects.
    void create(int i) {
        for(int j=0; j<10; j++) {
            long index = j+i*10;
            sq[j] = new Square(index);
            hashtable.put(sq[j].num, sq[j]);
        }
    }
    
    // Use Square objects.
    void use(int i) {
        for(int j=0; j<10; j++) {
            System.out.print(((Square)(hashtable.get(sq[j].num))).square + " ");
        }
        System.out.println();
    }
    
    void release() {
        for(int j=0; j<10; j++) {
            sq[j] = null;
        }
    }
    
}

class Square {
    String num;
    String square;
    public Square(long num) {
        this.num = new Long(num).toString();
        this.square = new Long(num*num).toString();
    }
}

Assume the code works in the following way, although you may already notice the problem of this sample code. :)

  • Create Square objects.
  • Store created Square objects in Hashtable.
  • Release Square objects after using them.

Finally, it releases Square objects, so it seems to have no memory leak issue.

Problem: OutOfMemoryError

Now, let's run the sample code. If there is no problem, it should display the square of numbers through 1 to 999999. Here is the execution result.

[Execution Result]


0 1 4 9 16 25 36 49 64 81 
	:
	snip
	:
179411544900 179412392041 179413239184 179414086329 179414933476 
179415780625 179416627776 179417474929 179418322084 179419169241 
Exception in thread "main" java.lang.OutOfMemoryError

Unfortunately, the application is terminated due to OutOfMemoryError Exception.

Use Profiler

Why the sample application was terminated due to OutOfMemoryError? We can guess the application exhausted the jvm heap, because it is OutOfMemoryError. However, how and which object causes this error?

This is the time to use NetBeans Profiler. It profiles allocating and deallocating new objects. To run the profiler, select the application's main class in the Explorer window and choose Profile -> Profile File...

Then, choose Analyze Memory Usage. Choose Record both objects and garbage collection.

Heap graph in 'Profiler' window

The application is running with the Profiler. To see the profiler statistics, choose Profile -> Get Current Results. As the first step, look at 'Profiler' window at the bottom of whole window. In Heap graph in 'Profiler' window, we can see the size of used heap (purple color) continue to grow. So, it has possibly a memory leak problem.

[Current Results image]

Entire Netbeans Window

Live Objects Memory graph

Next, look at the Live Objects Memory graph in the above figure. There are four objects that occupy the most of memory. They are char[], String, Hashtable$Entry and Square. Also look at the 'Generations' field in the above figure. This field essentially represents the sign of a memory leak. If this number is increasing steadily, it likely means the existence of a memory leak. The four objects have a higher number in the Generations field. They are 51. By contrast, others are 1. Due to the higher number, I suspects they have not been released correctly and caused a memory leak.

Reverse call graph

Now, let's look at the reverse call graph of the 'char' object which is at the top of the list. You can do it by double-clicking the 'char' object item.

[Reverse call graph of char]

Entire NetBeans window

According to the reverse call graph, it seems that some of 'char' objects were created in String.<init>, but have never been released. Because 'char' created in Square.<init> has the same number in both 'Live objects' and 'Allocated objects' fields. Now, let's look at the reverse call graph of 'String' object.

[ Reverse call graph of String]

Entire NetBeans window

There is a similar situation to 'char'. Some of 'String' objects were created in Long.toString(), but have not been released. 'String' created in Long.toString() has the same number of Live and Allocated objects. Why these objects have never been released? The clue is in the list of live objects. In the list, 'Hashtable.$Entry' object also has higher Generations field. 'Hashtable.$Entry' is alive till Hashtable.remove() is called. And 'Hashtable.$Entry' has objects which are stored in Hashtable. Therefore, we can guess that objects which are stored in Hashtable have not been released appropriately.

Look at the inline code.

Then, let's go back to the source code of the sample program.

[Excerpt of source]


    void release() {
        for(int j=0; j<10; j++) {
            sq[j] = null;
        }
    }

Good! Now, we are very close to the root cause. 'Square' objects are correctly set to 'null'. However, we seemed to forget to release these 'Square' objects in Hashtable. The Hashtable has you. We can see a 'Square' object has a 'String' object. 'String' objects in 'Square' objects seem to remain in heap. To resolve this issue, I modify the 'release' method in the source code in the following way.

[Modified source]


    void release() {
        for(int j=0; j<10; j++) {
            hashtable.remove(sq[j].num);  /* added */
            sq[j] = null;
        }
    }

Let's run the corrected sample program.

[Result]


0 1 4 9 16 25 36 49 64 81 
 :
snip
 :
999980000100 999982000081 999984000064 999986000049 999988000036 
999990000025 999992000016 999994000009 999996000004 999998000001 

Wow, We see the sample program finished without any error. With the functionality of monitoring memory usage, NetBeans Profiler allows us to dig into the cause of memory leak problem.


4.Summary

I introduced a methodology and a case study of NetBeans Profiler to detect memory leak issues. Try latest NetBeans Profiler. It will help you analyze JVM memory issues. May the NetBeans Profiler with you.


By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2013, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo