Garbage collection, something not to worry, sometime to worry a lot. A novice Java developer starts writing first code without worrying a bit about memory utilized by the program. There is always this, I would say wrong notion, that garbage collector is there to clean up any memory consumed. But when the application runs out of memory, then the developer starts cursing Java for in-efficient java memory management. Is Java the culprit? Or it is the memory consumption by the program is problem. In this article we identify ten important points, that a developer must remember to conserve memory and reduce load on garbage collection, and finally to avoid out of memory or performance problems.

These points revolve around following principles -

  •          Create Objects with Care
  •          Free up When not Required
  •          Help Java Garbage Collector to do Job Easily

Here are the points with example.

Java Memory Management in Programs

1. Minimize Scope of Variables: Minimum scope means availability of object for garbage collection quickly. Let us take an example. We have a variable defined at method level, as soon as the method execution is complete i.e. control moves out of the method, the variable is no longer referenced. This means it is ready for clean up. But if this variable is at class level, then it is possible that the garbage collector waits until all references of class are removed. Optimum scope of an attribute is needed. This is also in line with encapsulation principle. Minimized scope reduces access of the variables across packages and reduces coupling.

2. Initialize When You Actually Need: This is allocating memory just before you actually use the object first time. Sometimes it is needed to declare some of the attributes at the beginning of method. While declaring such attributes, we tend to initialize those during declaration itself. In this scenario if anything goes wrong (e.g. exception occurs) before first use of this variable, then this is unnecessary initialization and waste of memory. Here is an example -

 

import java.util.Vector;

public class ExampleClass {

	public Vector getDataIncorrect() throws Exception{
		Vector data = new Vector();
		try{
			//Something to get data from database
		}catch(Exception ex){
			//Dummy exception conversion
			throw new Exception(ex);
		}
		//transform and populate data in the Vector
		//.....
		//......
		return data;
	}

	public Vector getDataCorrect() throws Exception{
		Vector data = null;
		try{
			//Something to get data from database
		}catch(Exception ex){
			//Dummy exception conversion
			throw new Exception(ex);
		}
		//transform and populate data in the Vector
		data = new Vector(5);
		data.add("Just an Example");
		//......
		return data;
	}
}

3. Allocate Only Required Memory: The best example of this scenario is Vector (java.util.Vector). This class has default initial size 10 on initialization. Once we go on adding objects to this collection and there is need to add addition memory, this self expanding collection adds another 10 memory spaces to itself. In this scenario we may require only 2-3 out of those 10. Hence it is necessary to identify the size initially and allocate that much memory only. In above sample code, we are initializing Vector at appropriate place with required size (5).

 

4. Do not Declare in Loops: This is another common mistake found in most of the programs running out of memory. Declaring and initializing variables inside loop. For each iteration of the loop, this variable instance is created in memory is allocated through initialization. As soon as the second iteration starts, the first object is ready for clean up. This additional memory allocation adds load to garbage collector. Take a look at following code snippet and you can find the problem easily. Sometimes it is necessary to declare and initialize in loops, but this call should be taken judiciously.

import java.util.Vector;

public class DeclareInLoopExample {

	public void getDataIncorrect() throws Exception{
		Vector data = new Vector(11);
		for (String str : data){
			String newStr = "";
			//...Some string operations
			newStr = newStr + "X" + "X";
		}
	}

	public void getDataCorrect() throws Exception{
		Vector data = new Vector(11);
		String newStr = "";
		for (String str : data){
			//...Some string operations
			newStr = newStr + "X" + "X";
		}
	}

}

 

5. Memory Leaks Possibilities Through Soft References in Collections: There can be more reasons for memory leak, but this is one of the common scenarios.  In following example, can you spot the memory leak problem?

private Object[] elements;
// Some code to add elements
elements[--size];
return elements;

Here the collection object at elements[size] location is not garbage collected because of the soft reference to this collection after reduction of size. Following code can fix this problem.

private Object[] elements;
// Some code to add elements
elements[--size];
elements[size] = null;
return elements;

6. Mutating Operations on String: This is something special about String, all operations on string result in another string object. Mostly we have string concatenation operation carried out, it can be to generate a long dynamic query or to generate a string to print in toString() method (we override from object class). If we use the ‘+’ operator to concatenate string, those many additional objects will be created as the number of + signs. This becomes costly when there are many such operations. Use other alternatives which do not mutate like this. Here StringBuffer can be used to concatenate string.

 

7. Clean up Heavy Objects: Make it little bit easy for garbage collector. Set the heavy objects to null. This will free up the references of heavy object and make that memory available immediately.

 

8. Simply Use finally Block: This block gets executed even if the try or catch do not succeed. i.e. even if there is any exception thrown in try-catch blocks, the statements in ‘finally’ block definitely get executed. Write all statements to free up memory in this block to ensure the completion of operation.

 

9. ‘finalize’ Method Call Has Advantages and Disadvantages: This method is available in object class. Java suggests that this method should be called when you want to instruct garbage collector to clean up the object. Keep in mind that this method does not trigger the clean up immediately. It just intimates JVM that the object is ready to free up memory. Garbage collection happens at the JVM’s/garbage collector’s luxury. But too many such calls can result in triggering of garbage collection frequently and resulting in reducing performance of main program. The reason is simple- garbage collector is also a program which utilizes the machine resources. Hence it is better to reduce usage of memory instead of increasing load on cleanup operation.

10. Design and Architecture requiring more Memory: This is where the things can go fundamentally wrong, and even after putting lot of effort in reducing memory usage by tuning the code, you will get very little success. The design and architecture should be such that you have to minimum data loaded in memory. Sometimes you have to load data in memory to avoid repeated calls to persistent store/source of data, but it is better to optimize this at architecture and design stage only, instead of leaving it to create trouble in testing phase and you ending up putting heavy correction cost.

To summarize, memory problems in applications are sometime due to allocation of less memory to JVM, but mostly because of code problems or design and architecture problems that we introduce. With above small tips being followed while writing application, one can get rid of a lot of pain in later stages.

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS