1. 核心概念定义Java虚拟机栈是描述 Java 方法执行过程的线程内存模型。作用主管 Java 程序的运行保存方法的局部变量、部分结果并参与方法的调用和返回。生命周期与线程生命周期保持一致。线程创建时栈被创建线程结束时栈自动释放。特性线程私有每个线程拥有独立的栈空间互不干扰因此不存在线程安全问题。无垃圾回收栈内存只涉及栈帧的压入和弹出不需要 GC 介入。2. 内部结构栈帧栈帧是虚拟机栈的基本存储单位。每个 Java 方法的执行都对应一个栈帧在虚拟机栈中从入栈到出栈的过程。一个栈帧主要包含以下四个部分(1).局部变量表这是一组变量值的存储空间用于存放方法参数和方法内部定义的局部变量存储内容基本数据类型boolean, byte, char, short, int, float, long, double。对象引用reference 类型指向堆中对象的地址。返回地址类型returnAddress 类型指向字节码指令的地址存储单位变量槽这是局部变量表的最小单位。32位以内类型占用1个 Slot。64位类型long, double占用2个 Slot。引用访问通过索引定位。例如索引0通常是this引用仅限实例方法索引1是第一个参数依此类推。Slot 复用机制如果当前字节码指令的位置已经超过了某个变量的作用域那么该变量对应的 Slot 可以被后续定义的变量重新使用。这不仅节省栈空间还能通过覆盖引用帮助 GC 及时回收对象。(2).操作数栈这是一个后进先出的栈结构是方法执行过程中的临时计算工作区。工作机制方法刚开始执行时操作数栈是空的。执行过程中字节码指令会将数据从局部变量表或常量池复制到操作数栈中。CPU 指令从栈顶弹出数据进行计算并将结果重新压入栈顶。数据流转Java 虚拟机的解释引擎是基于栈的执行引擎大部分指令都是围绕操作数栈工作的。(3).动态连接每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用。目的为了支持方法调用过程中的动态连接。转换过程静态解析类加载阶段符号引用转为直接引用针对静态方法、私有方法等。动态连接运行期间将符号引用转换为直接引用针对接口方法、虚方法等。(4).方法返回地址存放调用该方法的 PC 寄存器的值。正常退出调用者的 PC 计数器的值作为返回地址即调用该方法的指令的下一条指令地址。异常退出通过异常处理表来确定栈帧中一般不保存异常退出的返回信息。3.常见异常(1).StackOverflowError定义线程请求的栈深度大于虚拟机允许的深度。常见原因无限递归循环。递归层级过深如斐波那契数列的暴力递归。方法定义了过多的局部变量导致单个栈帧过大。(2).OutOfMemoryError定义虚拟机栈动态扩展时无法申请到足够的内存。常见原因系统内存耗尽。线程数过多。计算公式MaxThreadNum \approx \frac{TotalMemory - HeapMemory - MaxPermSize}{StackSize}从中可以看出在总内存固定的情况下减少单个线程的栈大小StackSize可以增加理论上的最大线程数。4.常用JVM参数-Xss含义设置每个线程的栈内存大小。默认值通常为1MBLinux 64位系统。调优策略如果是高并发、方法调用链短的业务如简单的 Web 接口可将此值调小256k以支持更多线程。如果是深度递归或复杂算法业务需保持默认或适当调大。5.如何排查高CPU占用与死循环第一步定位高 CPU 的进程使用top命令查看系统资源使用情况。命令top操作按Shift P可以让列表按 CPU 使用率排序。观察找到%CPU列最高的那个进程记录下它的PID(Process ID)。假设 PID 为 12345。第二步定位高 CPU 的线程一个 Java 进程里可能有几十上百个线程我们需要知道具体是哪个线程在占资源。命令top -Hp 12345H显示线程视图。p指定进程 ID。观察列表会变成线程视图。同样找到%CPU最高的那个线程记录下它的TID。假设最忙的 TID 为 12368。第三步将 TID 转换为十六进制因为 Java 的堆栈工具jstack中线程 ID 是以十六进制形式显示的而 Linux 的top显示的是十进制。我们需要手动转换。命令printf %x\n 12368结果假设输出为3050。这是我们在日志里要搜的关键字。第四步导出堆栈信息并定位代码使用 JDK 自带的工具jstack导出当前进程的所有线程堆栈然后用刚才得到的十六进制 TID 去搜索。命令Bashjstack 12345 | grep -A 20 305012345是进程 PID。grep 3050是搜刚才转化的十六进制 TID。A 20表示显示匹配行之后的 20 行也就是这个线程的堆栈详情。