网站首页 > java教程 正文
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工具来告诉您有多少个活动线程以及处于休眠状态的线程。它还可以告诉您每个线程的堆栈有多深。根据这些报告,您可以为应用程序提供最佳的线程数和线程的堆栈大小。
作者:令狐义卓
链接:
https://juejin.cn/post/6936043006566858782
来源:掘金
猜你喜欢
- 2025-10-14 看完这篇文,别再说你不懂Java内存模型了!
- 2025-10-14 Java volatile关键字深度解析:多线程编程的"内存屏障"神器
- 2025-10-14 Java内存模型JMM重要知识点_java内存模型有哪些
- 2025-10-14 Java 内存模型与并发编程中的可见性、原子性、有序性有啥关联
- 2025-10-14 让我们深入了解有关Java内存泄漏的10件事情
- 2025-10-14 Java中的volatile与操作系统的内存重排详解
- 2025-10-14 Java内存模型的历史变迁_java内存模型原理
- 2025-10-14 Kubernetes 下 Java 应用内存调优实战指南
- 2025-10-14 java使用NMT Native Memory Tracking分析内存占用
- 2025-10-14 【java面试100问】03 在生产环境上,发现内存泄漏问题,如何排查?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)