JVM--栈
JVM–栈
背景
-
JVM–栈
-
博主以黑马JVM进行学习
定义
- 线程运行需要的内存空间(线程私有,每个线程都有独立的虚拟机栈,互不干扰)
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 栈帧:每个方法运行时需要的内存(一个栈帧就对应一次方法的调用)(局部变量表、操作数栈、动态链接、方法返回地址)
- 每个线程只能有一个活动栈帧(在顶部),对应着当前正在执行的那个方法
问题
-
垃圾回收是否涉及栈内存?
- 不会也不需要,方法执行完毕后,对应的栈帧会直接出栈并释放内存,属于自动内存释放,不需要 GC 参与 ——GC 只负责堆内存的回收
-
栈内存分配越大越好吗?
- 不会,JVM 总内存固定,栈内存通过
-Xss参数设置(默认约 1M),栈内存越大,单个线程占用的内存越多,能创建的线程总数就越少,容易导致OutOfMemoryError: unable to create new native thread
- 不会,JVM 总内存固定,栈内存通过
-
方法内的局部变量是否线程安全?
- 核心结论:方法内的普通局部变量(非静态、非逃逸)是线程安全的;如果局部变量逃逸出方法(比如返回给外部、传递给其他线程),则可能不安全;
- 判断是否线程安全,先判断是否是静态变量,再看是否逃离了当前线程(返回了)
栈的内存溢出
- 栈帧过多导致栈的内存溢出
- 无限递归调用、方法嵌套调用层数过深时容易出现
- 抛出异常:
StackOverflowError - 排查递归终止条件、减少方法嵌套深度、适当调大
-Xss(但不推荐,治标不治本);
- 栈帧过大导致内存溢出(不太容易出现)
- 方法内定义大量 / 超大局部变量(比如局部变量表中存储超大数组)
- 抛出异常:
StackOverflowError(注意:栈溢出是StackOverflowError,而非OutOfMemoryError,只有创建线程过多导致栈内存总容量不足时,才会抛 OOM)。
线程运行诊断
- cpu占用过多
- Linux的命令:
- top:监测cpu的占用,定位到进程id
- ps H -eo pid,tid,%cpu | grep 进程id:查看该进程下所有线程的 TID(线程 ID,十进制)和 CPU 占用率,定位到高占用线程的 TID;
- jstack线程id
- 导出线程栈信息,根据十六进制 TID 找到对应的线程,查看栈帧信息,定位到占用 CPU 过高的代码行(通常是死循环、频繁递归);
- Linux的命令:
- 程序运行很长时间没有结果
- 常见原因:死锁、死循环、线程阻塞(比如等待锁 / IO);
- 使用jstack 线程id
- 找到Found one Java-level deadlock:
- 常见原因:死锁、死循环、线程阻塞(比如等待锁 / IO);
本地方法栈Native Method Stacks
- 为本地方法(
native修饰的方法,比如Object.getClass()、System.currentTimeMillis())提供内存空间; - 线程私有,和虚拟机栈逻辑类似,但服务于本地方法;
- 异常:同样会抛出
StackOverflowError或OutOfMemoryError; - 注意:HotSpot 虚拟机将 “虚拟机栈” 和 “本地方法栈” 合并实现,因此
-Xss参数同时控制两者的内存大小。
总结
- 虚拟机栈是线程私有内存,栈帧对应方法调用,方法执行完栈帧自动释放内存,无需 GC 参与;
- 栈溢出(
StackOverflowError)主要由无限递归导致,栈内存过大反而会减少线程总数; - 线程诊断核心工具是
top/ps/jstack,CPU 高查死循环 / 频繁递归,无响应查死锁 / 线程阻塞; - 地方法栈为 native 方法提供内存,HotSpot 中与虚拟机栈合并,共用
-Xss配置。