Java线程:它们的内存效率高吗?

96 篇文章 2 订阅
93 篇文章 3 订阅

Java应用程序倾向于包含数百个(有时是数千个)线程。这些线程中的大多数处于WAITING或TIMED_WAITING(即休眠)状态,而只有一小部分正在主动执行代码行。因此,我们很想知道休眠线程是否比活动线程消耗更少的内存。

为了弄清楚这个问题的答案,我进行了一项小型研究。

线程堆栈中存储了什么?

在继续阅读之前,您应该首先知道线程堆栈中存储了哪些信息。要完整了解线程堆栈中存储的信息。简而言之,以下内容存储在线程的堆栈中:

1,在方法中创建的局部变量。

2,线程当前正在执行的代码路径。

学习

为了方便我们的学习,我们编写了两个简单的程序。让我们回顾这两个程序及其性能特征。

1.带有空堆栈框架的线程

创建了一个简单的Java程序,它将创建1000个线程。该程序中的所有线程的堆栈帧几乎都为空,因此不必消耗任何内存。

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) {      
     }   
   } 
}

在此Java程序中,EmptyStackFrameProgram该类中创建了1000个线程。所有EmptyStackFrameThread线程都进入无限睡眠状态,并且它们什么也不做。这意味着它们的堆栈框架将几乎为空,因为它们没有执行任何新的代码行或创建任何新的局部变量。

注意: 我们将线程置于无限睡眠状态,以便它们不会消失,这对于研究其内存使用情况至关重要。

2.带有已加载堆栈框架的线程

这是另一个简单的Java程序,它将创建1000个线程。该程序中的所有线程都将在堆栈帧中完全加载数据,因此它们将比早期程序消耗更多的内存。

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);      
    }         
  } 
}

在此Java程序中,FullStackFrameProgram该类中创建了1000个线程。所有FullStackFrameThread线程均调用simpleMethod(int counter)10,000次。10,000次调用后,线程将进入无限睡眠状态。由于线程调用simpleMethod(int counter)10,000次,因此每个线程将具有10,000个堆栈帧,并且每个堆栈帧都将被局部变量’x’,‘y’,'z’填充。在这里插入图片描述
上图显示了EmptyStackFrameThread的堆栈和的可视化FullStackFrameThread。您会注意到EmptyStackFrameThread其中仅包含两个堆栈帧。另一方面,FullStackFrameThread包含10,000+个堆栈帧。除此之外,的每个堆栈帧都FullStackFrameThread将包含局部变量x,y,z。这将导致FullStackFrameThread堆栈已满载。因此,人们会期望FullStackFrameThread堆栈消耗更多的内存。

内存消耗

我们使用以下设置执行了以上两个程序:

1,将线程的堆栈大小配置为2 MB(即,将-Xss2m JVM参数传递给两个程序)。

2,使用OpenJDK 1.8.0_265、64位服务器VM。

3,在AWS’t3a.medium’EC2实例上同时运行两个程序。

在下面,您可以查看系统监视工具“ top”报告的程序内存消耗。在这里插入图片描述
会注意到这两个程序正消耗4686 MB的内存。这表明两个程序线程都消耗相同的内存量,即使该程序线程处于FullStackFrameThread活动状态,而EmptyStackFrameThread几乎处于休眠状态。
为了验证该理论,我们使用JVM根本原因分析工具yCrash进一步分析了这两个程序。以下是yCrash工具生成的线程分析报告。在这里插入图片描述
在这里插入图片描述
yCrash还清楚地指出EmptyStackFrameProgram包含两个堆栈框架的FullStackFrameProgram1,000个线程,而包含10,000堆栈框架的1,000个线程。

结论

这项研究清楚地表明,内存是在创建时分配给线程的,而不是根据线程的运行时需求分配的。超级工作线程和几乎休眠的线程都消耗相同数量的内存。现代Java应用程序倾向于创建数百个(有时数千个)线程。但是这些线程大多数都处于WAITING或TIMED_WAITING状态,并且什么也不做。鉴于线程在创建时会立即占用分配的最大内存量,作为应用程序开发人员,您可以执行以下操作来优化应用程序的内存消耗:

1,仅为您的应用程序创建必要线程。

2,尝试为您的应用程序线程提供最佳的堆栈大小(即-Xss)。因此,如果将线程的堆栈大小(即-Xss)配置为2 MB,并且在运行时您的应用程序仅使用512 KB,则将为应用程序中的每个线程浪费1.5 MB的内存。如果您的应用程序有500个线程,则每个JVM实例将浪费750 MB(即500个线程x 1.5 MB)的内存,这在现代云计算时代并不便宜。

您可以使用诸如yCrash工具来告诉您有多少个活动线程以及处于休眠状态的线程。它还可以告诉您每个线程的堆栈有多深。根据这些报告,您可以为应用程序提供最佳的线程数和线程的堆栈大小。

更多Java基础技术学习和交流,可以加入我的十年Java学习园地

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值