Java applications tend to contain hundreds (and sometimes thousands) of threads. Most of these threads are WAITING or TIMED_WAITING (dormant), and only a small number are actively executing lines of code. Therefore, it would be interesting to know whether dormant threads consume less memory than active threads.

To find out, I conducted a small study.

What is stored in the thread stack?

Before you continue, you should first know what information is stored in the thread stack. To fully understand the information stored in the thread stack. In short, the following is stored in the thread stack:

1. Local variables created in a method.

2. The path of code that the thread is currently executing.

learning

In order to facilitate our learning, we wrote two simple procedures. Let’s review these two programs and their performance characteristics.

1. Thread with empty stack frame

Creates a simple Java program that will create 1000 threads. The stack frames for all threads in the program are nearly empty, and therefore do not consume any memory.

public class EmptyStackFrameProgram { public void start() { // Create 1000 threads for (int counter = 0; counter < 1000; ++counter) { new EmptyStackFrameThread().start(); } } } public class EmptyStackFrameThread extends Thread { public void run() { try { // Just sleep forever while (true) {  Thread.sleep(10000); } } catch (Exception e) { } } }Copy the code

In this Java program, 1000 threads are created in the EmptyStackFrameProgram class. All EmptyStackFrameThread threads go into infinite sleep, and they do nothing. This means that their stack frames will be almost empty because they haven’t executed any new lines of code or created any new local variables.

Note: We put the threads into an infinite sleep state so that they don’t disappear, which is crucial for studying their memory usage.

2. Thread with loaded stack frame

This is another simple Java program that will create 1000 threads. All threads in the program will fully load data in stack frames, so they will consume more memory than earlier programs.

public class FullStackFrameProgram {    
   public void start() {               
   // Create 1000 threads with full stack      
   for (int counter = 0; counter < 1000; ++counter) {         
       new FullStackFrameThread().start();      
     }   
   } 
}
 public class FullStackFrameThread extends Thread {    
    public void run() {   
       try {         
          int x = 0;         
          simpleMethod(x);      
          } catch (Exception e) {     
     }   
}   
 /**    
  * Loop for 10,000 times and then sleep. So that stack will be filled up.    
  *    
  * @param counter    
  * @throws Exception    
  */   
private void simpleMethod(int x) throws Exception {       
    // Creating local variables to fill up the stack.     
    float y = 1.2f * x;      
    double z = 1.289898d * x;            
   // Looping for 10,000 iterations to fill up the stack.            
   if (x < 10000) {        
       simpleMethod(++x);      
   }            
   // After 10,000 iterations, sleep forever      
   while (true) {         
      Thread.sleep(10000);      
    }         
  } 
}
Copy the code

In this Java program, 1000 threads are created in the FullStackFrameProgram class. All FullStackFrameThread threads call simpleMethod(int Counter)10,000 times. After 10,000 calls, the thread goes into an infinite sleep. Since threads call simpleMethod(int Counter)10,000 times, each thread will have 10,000 stack frames, and each stack frame will be filled with local variables’ x ‘, ‘y’, ‘z ‘.

The figure above shows the stack and visualization of the FullStackFrameThread for EmptyStackFrameThread. You’ll notice that EmptyStackFrameThread contains only two stack frames. FullStackFrameThread, on the other hand, contains 10,000+ stack frames. In addition, each stack frame of FullStackFrameThread will contain local variables X, y, and z. This will cause the FullStackFrameThread stack to be fully loaded. Therefore, one would expect the FullStackFrameThread stack to consume more memory.

Memory consumption

We executed the above two programs using the following Settings:

1, configure the stack size of the thread to be 2 MB (that is, pass the -xSS2m JVM argument to both programs).

2, use OpenJDK 1.8.0_265, 64-bit server VM.

3. Run both programs simultaneously on an AWS ‘TP3a. Medium’ EC2 instance.

Below, you can view the program memory consumption reported by the system monitoring tool “TOP.”

You’ll notice that these two programs are consuming 4686 MB of memory. This indicates that both program threads consume the same amount of memory, even though the program thread is FullStackFrameThread active and the EmptyStackFrameThread is almost dormant. To test this theory, we further analyzed the two programs using the JVM root-cause analysis tool yCrash. Below is the thread analysis report generated by the yCrash tool

YCrash also clearly pointed out that EmptyStackFrameProgram contains two stack frame FullStackFrameProgram1, 000 threads, and contains 10000 stack frame 1000 threads.

conclusion

This study clearly shows that memory is allocated to threads at the time of creation, not based on their runtime needs. Both super worker threads and nearly dormant threads consume the same amount of memory. Modern Java applications tend to create hundreds (and sometimes thousands) of threads. But most of these threads are in WAITING or TIMED_WAITING and do nothing. Since threads immediately take up the maximum amount of memory allocated when they are created, as an application developer, you can optimize your application’s memory consumption by:

1. Create only the necessary threads for your application.

2, try to provide the best stack size (-xSS) for your application threads. Therefore, if you configure the stack size (-xSS) for the threads to be 2 MB, and your application uses only 512 KB at run time, you will waste 1.5 MB of memory for each thread in your application. If your application has 500 threads, each JVM instance will waste 750 MB (500 threads x 1.5 MB) of memory, which is not cheap in the modern cloud computing era.

You can use tools such as yCrash to tell you how many active threads there are and which ones are dormant. It also tells you how deep the stack is for each thread. Based on these reports, you can provide the optimal number of threads and stack size for your application.

For more basic Java technology learning and communication, you can join my ten-year Java Learning Garden.