Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

  • Java集合

  • Java并发

  • Java IO

  • JVM

    • JVM概述
    • JVM基础

    • 类加载机制

    • 运行时内存

      • Java堆
      • 方法区与元空间
      • 虚拟机栈本地方法栈与程序计数器
        • 1. 线程私有是什么意思
        • 2. 虚拟机栈
        • 3. 栈大小参数
        • 4. 本地方法栈
        • 5. 程序计数器
        • 6. 无法创建新线程
        • 7. 阶段小结
        • 8. 线程私有区域结构图
        • 9. -Xss 和线程数量估算
        • 10. 常见线程栈问题
          • 10.1 深递归
          • 10.2 线程池失控
          • 10.3 栈上引用影响 GC
        • 11. Tips 快问快答
      • 对象创建与内存布局
      • 直接内存
      • OOM分类与定位
    • 垃圾回收

    • 执行引擎与优化

    • 工具与问题排查

    • 调优实践与面试

目录

虚拟机栈本地方法栈与程序计数器

除了堆和方法区,JVM 中还有几个线程私有的运行时区域:虚拟机栈、本地方法栈和程序计数器。它们主要服务于线程执行和方法调用。

# 1. 线程私有是什么意思

线程私有表示每个线程都有自己独立的一份内存区域。

线程A -> 自己的程序计数器、虚拟机栈、本地方法栈
线程B -> 自己的程序计数器、虚拟机栈、本地方法栈

线程私有区域通常不需要 GC 管理,因为它们随线程创建和销毁。

# 2. 虚拟机栈

虚拟机栈保存 Java 方法调用过程。

每次方法调用创建一个栈帧:

methodA -> methodB -> methodC

对应栈结构:

methodA栈帧
methodB栈帧
methodC栈帧

当前执行的是栈顶方法。

# 3. 栈大小参数

线程栈大小可以通过参数设置:

-Xss1m

栈越大:

  • 单个线程可承载更深调用。
  • 同样内存下可创建线程数减少。

栈越小:

  • 可创建更多线程。
  • 更容易出现 StackOverflowError。

# 4. 本地方法栈

本地方法栈用于执行 native 方法。

Java 标准库和 JVM 内部很多底层能力会涉及 native 方法,例如:

  • 文件系统。
  • Socket。
  • 压缩。
  • 加密。
  • 系统时间。

本地方法栈异常也可能表现为 StackOverflowError 或 OutOfMemoryError。

# 5. 程序计数器

程序计数器保存当前线程执行到哪条字节码指令。

多线程切换时,CPU 会在线程之间切换执行。程序计数器帮助线程恢复后继续从正确位置执行。

它的特点:

  • 线程私有。
  • 占用空间小。
  • 不会发生 OOM。
  • native 方法时值通常未定义。

# 6. 无法创建新线程

大量线程可能导致:

java.lang.OutOfMemoryError: unable to create native thread

这不一定是 Java 堆不够,而可能是:

  • 操作系统线程数限制。
  • 进程可用内存不足。
  • 单线程栈过大。
  • 容器资源限制。

排查时要看线程数、-Xss、系统限制、容器内存。

# 7. 阶段小结

虚拟机栈负责 Java 方法调用,本地方法栈负责 native 方法调用,程序计数器负责记录线程执行位置。它们是线程私有区域,常见问题包括栈溢出和无法创建新线程。

# 8. 线程私有区域结构图

Thread-1
├─ PC Register
├─ Java Virtual Machine Stack
│  ├─ frame: controller()
│  ├─ frame: service()
│  └─ frame: repository()
└─ Native Method Stack

Thread-2
├─ PC Register
├─ Java Virtual Machine Stack
└─ Native Method Stack

每个线程都有独立的执行位置和调用栈,因此一个线程抛出栈溢出,不代表其他线程栈也有问题。

# 9. -Xss 和线程数量估算

假设进程可用于线程栈的本地内存约为 512MB:

-Xss 理论可支持线程数上限
1MB 约 512 个
512KB 约 1024 个
256KB 约 2048 个

这是非常粗略的估算,实际还受操作系统、容器、JVM 内部内存、线程库开销影响。

工程上更重要的是:不要靠无限创建线程解决并发问题,应该控制线程池。

# 10. 常见线程栈问题

# 10.1 深递归

void dfs(Node node) {
    for (Node child : node.children()) {
        dfs(child);
    }
}

如果树深度不可控,可能栈溢出。可以改成显式栈:

Deque<Node> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
    Node node = stack.pop();
    // process
}

# 10.2 线程池失控

如果每个请求都创建线程,或者线程池最大线程数过高,可能导致 native thread OOM。

# 10.3 栈上引用影响 GC

栈帧中的局部变量引用是 GC Roots。一个长时间运行的方法如果持有大对象引用,即使后面逻辑不再使用,也可能延长对象生命周期。

# 11. Tips 快问快答

Q:StackOverflowError 能不能靠加大 -Xss 解决?

A:只能缓解,不能解决无限递归或不合理递归深度。根因通常要改代码。

Q:为什么线程太多会导致 OOM,而不是 CPU 高?

A:线程需要本地栈内存和系统线程资源,数量过多会先耗尽内存或线程配额。

Q:程序计数器会发生 OOM 吗?

A:JVM 规范中程序计数器是唯一没有规定 OOM 的运行时区域。

Q:RUNNABLE 线程一定在占 CPU 吗?

A:不一定。它可能在运行,也可能在 native IO 等待。要结合系统线程 CPU 使用判断。

上次更新: 2026/06/24, 16:03:01
方法区与元空间
对象创建与内存布局

← 方法区与元空间 对象创建与内存布局→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式