JVM--栈

JVM–栈

背景

  • JVM–栈

  • 博主以黑马JVM进行学习

定义

  • 线程运行需要的内存空间(线程私有,每个线程都有独立的虚拟机栈,互不干扰)
  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 栈帧:每个方法运行时需要的内存(一个栈帧就对应一次方法的调用)(局部变量表、操作数栈、动态链接、方法返回地址)
  • 每个线程只能有一个活动栈帧(在顶部),对应着当前正在执行的那个方法

问题

  • 垃圾回收是否涉及栈内存?

    • 不会也不需要,方法执行完毕后,对应的栈帧会直接出栈并释放内存,属于自动内存释放,不需要 GC 参与 ——GC 只负责堆内存的回收
  • 栈内存分配越大越好吗?

    • 不会,JVM 总内存固定,栈内存通过 -Xss 参数设置(默认约 1M),栈内存越大,单个线程占用的内存越多,能创建的线程总数就越少,容易导致 OutOfMemoryError: unable to create new native thread
  • 方法内的局部变量是否线程安全?

    • 核心结论:方法内的普通局部变量(非静态、非逃逸)是线程安全的;如果局部变量逃逸出方法(比如返回给外部、传递给其他线程),则可能不安全
    • 判断是否线程安全,先判断是否是静态变量,再看是否逃离了当前线程(返回了)

栈的内存溢出

  • 栈帧过多导致栈的内存溢出
    • 无限递归调用、方法嵌套调用层数过深时容易出现
    • 抛出异常: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 过高的代码行(通常是死循环、频繁递归);
  • 程序运行很长时间没有结果
    • 常见原因:死锁、死循环、线程阻塞(比如等待锁 / IO);
      • 使用jstack 线程id
      • 找到Found one Java-level deadlock:

本地方法栈Native Method Stacks

  • 为本地方法(native 修饰的方法,比如 Object.getClass()System.currentTimeMillis())提供内存空间;
  • 线程私有,和虚拟机栈逻辑类似,但服务于本地方法;
  • 异常:同样会抛出 StackOverflowErrorOutOfMemoryError
  • 注意:HotSpot 虚拟机将 “虚拟机栈” 和 “本地方法栈” 合并实现,因此 -Xss 参数同时控制两者的内存大小。

总结

  • 虚拟机栈是线程私有内存,栈帧对应方法调用,方法执行完栈帧自动释放内存,无需 GC 参与;
  • 栈溢出(StackOverflowError)主要由无限递归导致,栈内存过大反而会减少线程总数;
  • 线程诊断核心工具是 top/ps/jstack,CPU 高查死循环 / 频繁递归,无响应查死锁 / 线程阻塞;
  • 地方法栈为 native 方法提供内存,HotSpot 中与虚拟机栈合并,共用 -Xss 配置。