Hello,

I would update my previous article concerning a solution in order to avoid the famous OutOfMemoryError: Java heap space or OutOfMemoryError: PermGen space errors.

This error java.lang.OutOfMemoryError in Java is a subclass of java.lang.VirtualMachineError which is thrown by the Java Virtual Machine when it ran out of memory in heap or it cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector, for example:

  • when not enough memory can be allocated for a new array;
  • when not enough memory is available for your class to load;
  • when too many threads / not enough stack space available for a new thread.

However, it is possible to avoid this error by verifying the memory status of JVM. So, we have created a simple technical component Memory Checker whose the role is to check the available memory space in VM for the execution of Java code in order to avoid these “out of memory” errors. If there is enough memory, the current thread will be put on hold for the specified delay (between 2 retries).
 


 
As a reminder:

  • PermGen is the memory dedicated to the permanent objects in the VM (Class names, internalized strings, objects that will never get garbage-collected). So the -XX:PermSize=128M parameter corresponds to the memory reserved at the application server starting time. The -XX:MaxPermSize parameter corresponds to the maximum memory usable by the server before swaping.
  • -Xms1024m corresponds to the memory reserved at the application server starting time for the non permanent (volatile) objects in the VM.
  • -Xmx4096m corresponds to the maximum memory usable by the server for the non permanent (volatile) objects in the VM, if this limit is reached, the server persists the data on disk via swaping.

…so, at the application server starting time, the memory reserved will be the total of -XX:PermSize and -Xms parameters.
 


 
I. Memory Checker
So, we will create a class named MemoryChecker:

public class MemoryChecker {
	// Number of tests of memory allocation 
	// for treatment before returning an OutOfMemory error
	private static int NB_OF_RETRY = 5;
	
	// If there is enough memory, 
	// the thread will be put on hold 
	// for the specified delay (between 2 retries).
	private static int WAITING_DELAY_MS = 500;

//...
}

…this class will contain a private static method logMemoryStatusInConsole which will print the memory status in console, eg the total amount of memory in the JVM, the maximum amount of memory that the JVM will attempt to use and the amount of free memory in the JVM:

/**
 * Print the memory status in console
 * @param threadName
 */
private static void logMemoryStatusInConsole(String threadName){
	/*
	 	Used_now = Total_now - Free_now
		Free_real = Max - Used_now
		Max => parameter JVM '-Xmx' (ex: -Xmx1G)
		Total => parameter JVM '-Xms' (ex: -Xms1G)
		The total memory is reserved at the application server starting time.
		Blocking condition to execute the java main:  maxMemory >= totalMemory
	 */

	Runtime rt = Runtime.getRuntime();
		
	// Returns the total amount of memory in the Java virtual machine.
	// parameter Xms ; (ex: -Xms1G)
	long totalNow = rt.totalMemory(); 

	// Returns the maximum amount of memory that the Java virtual machine will attempt to use
	// parameter Xmx ; (ex: -Xmx1G)
	long maxPossible = rt.maxMemory();

	// Returns the amount of free memory in the Java Virtual Machine.
	long freeNow = rt.freeMemory();
		
	long usedNow = totalNow - freeNow;
	long freeRealPossible = maxPossible - usedNow;
		
	//
	System.out.println(threadName + " : " + "memorybroker : free "+ freeRealPossible/1024 + "ko : (total/max) " + totalNow/1024 + "ko/" + maxPossible/1024+"ko");
}

…this class will contain a public static method checkMemoryAndIfNecessaryPutThreadOnHold which will check if there is enough memory to process the given number of bytes. If there is enough memory, the current thread will be put on hold for the specified delay.

{
	int nbLoop = NB_OF_RETRY;
			
	while(nbLoop>0){
		long freeNow = Runtime.getRuntime().freeMemory(); //
		long totalNow = Runtime.getRuntime().totalMemory(); // parameter Xms
		long maxPossible = Runtime.getRuntime().maxMemory(); // parameter Xmx
		long usedNow = totalNow - freeNow;
		long freeRealPossible = maxPossible - usedNow;
					
		if(freeRealPossible >= memorySize){
			// At this stage, the amount of memory required is available
			break;
						
		}else{
			System.out.println(threadName + " : " + memorySize/1024 + "ko : Not enough memory for the current thread, try again in " + WAITING_DELAY_MS + " ms");
			try{
				// Sleep the current thread
				Thread.sleep(WAITING_DELAY_MS);
			}catch(Throwable e1){
			}//end-try
			nbLoop--;
		}
	}//end-while


	if(0 == nbLoop){
		System.out.println(threadName + " : " + memorySize/1024 + "ko : Not enough memory for the current thread despite testing, so the propagation of OutOfMemoryError will be done.");
		// Print the memory status in console
		logMemoryStatusInConsole(threadName);
		throw new OutOfMemoryError();
	}//end-if

}//end-block

memorycheckerJVM
 


 
II. Test Memory Checker
We will create a main method in order to simulate multiple threads which call load a file (size of bytes) into memory Java (JVM):

// Number of threads
int nbThreads = 10;

// Memory needed for each thread
final int memorySizeNeeded = 300000*1024;

// Creation of threads
ExecutorService exec = Executors.newFixedThreadPool(nbThreads);
for (int i = 0; i < nbThreads; i++) {
	final int valueI = i;
			
	Runnable runnable = new Runnable(){
		private String threadName = null;
		public void run(){
			this.threadName = "thread_"+valueI;
					
			try{
				System.out.println(threadName + ":-- START --");

				// Memory status before the memory checking.
				System.out.println(threadName + " : " + "Memory status before the memory checking");
				logMemoryStatusInConsole(threadName);

				// Memory checking AND bo the business
				{
					// Memory checking
					checkMemoryAndIfNecessaryPutThreadOnHold(memorySizeNeeded, threadName);
					// Business
					byte[] tab = new byte[memorySizeNeeded];
				}

				logMemoryStatusInConsole(threadName);
				System.out.println(threadName + ":-- END --");
						
			}catch(Throwable th){
				System.out.println(threadName + ":-- ERROR --" + th);
			}//end-try
		}//end-run
	};
	//end-Runnable
	exec.submit(runnable);//end-submit
}//end-for


Test n°1
With 2 threads and a 300Mo for the memory needed fo each thread:

// Number of threads
int nbThreads = 2;

// Memory needed for each thread
final int memorySizeNeeded = 300000*1024;

… results: there is not enough of memory for all 2 threads, so, an error OutOfMemoryError is thrown:

thread_0:-- START --
thread_1:-- START --
thread_1 : Memory status before the memory checking
thread_1 : memorybroker : free 252893ko : (total/max) 15872ko/253440ko
thread_1 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_0 : Memory status before the memory checking
thread_0 : memorybroker : free 252893ko : (total/max) 15872ko/253440ko
thread_0 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_1 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_0 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_1 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_0 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_1 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_0 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_1 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_0 : 300000ko : Not enough memory for the current thread, try again in 500 ms
thread_1 : 300000ko : Not enough memory for the current thread despite testing, so the propagation of OutOfMemoryError will be done.
thread_1 : memorybroker : free 252893ko : (total/max) 15872ko/253440ko
thread_0 : 300000ko : Not enough memory for the current thread despite testing, so the propagation of OutOfMemoryError will be done.
thread_0 : memorybroker : free 252893ko : (total/max) 15872ko/253440ko
thread_0:-- ERROR --java.lang.OutOfMemoryError
thread_1:-- ERROR --java.lang.OutOfMemoryError

Test n°2
With 2 threads and a 300ko for the memory needed fo each thread:

// Number of threads
int nbThreads = 2;

// Memory needed for each thread
final int memorySizeNeeded = 300*1024;

… results: there is enough of memory for all 2 threads, so, no error OutOfMemoryError thrown:

thread_0:-- START --
thread_1:-- START --
thread_1 : Memory status before the memory checking
thread_1 : memorybroker : free 252893ko : (total/max) 15872ko/253440ko
thread_1 : memorybroker : free 252593ko : (total/max) 15872ko/253440ko
thread_1:-- END --
thread_0 : Memory status before the memory checking
thread_0 : memorybroker : free 252593ko : (total/max) 15872ko/253440ko
thread_0 : memorybroker : free 252293ko : (total/max) 15872ko/253440ko
thread_0:-- END --

III. Conclusion
This MemoryChecker class must be used for sensitive areas of an application (created thread pool, webservices, servlets, file handling…). These “sensitive” points can be targeted via tools like jconsole and visualvm that monitor the activity (eg total number of thread) of JVM on the real-time, which can be a big help.

But, the number of threads in a JVM is not infinitely expandable, so, if the application needs a very large number of thread to meet demand, several options should be considered:

  1. First solution: the least expensive change, modify (if possible) the Xmx (and/or Xss) parameters of your server in order to may create more threads. Then, it is well advised to re-test your application.
  2. Second solution: modify the physical and/or logical architecture of your application:
    • If you are on 32bit machines, switch to 64 bits, so as to extend the size limit of the process;
    • Modify your application so that you can add more instances of the JVM. Make the application more scaleable horizontally;
    • Turn (if possible) your synchronous processes into asynchronous processes in order to better manage your resources and minimize your wait time threads.

Source: MemoryChecker.java

That’s all!!!

Huseyin OZVEREN