java栈溢出现象_JVM源码分析之栈溢出完全解读

2023-11-17

9d2b1f558d11290f54e85fc4115016b7.png

概述

之所以想写这篇文章,其实是因为最近有不少系统出现了栈溢出导致进程crash的问题,并且很隐蔽,根本原因还得借助coredump才能分析出来,于是想从JVM实现的角度来全面分析下栈溢出的这类问题,或许你碰到过如下的场景:

日志里出现了StackOverflowError的异常

进程突然消失了,但是留下了crash日志

进程消失了,crash日志也没有留下

这些都可能是栈溢出导致的。

如何定位是否是栈溢出

上面提到的后面两种情况有可能不是我们今天要聊的栈溢出的问题导致的crash,也许是别的一些可能,那如何确定上面三种情况是栈溢出导致的呢?

出现了StackOverflowError,这种毫无疑问,必然是栈溢出,具体什么方法导致的栈溢出从栈上是能知道的,不过要提醒一点,我们打印出来看到的栈可能是不全的,因为JVM里对栈的输出条数是可以控制的,默认是1024,这个参数是-XX:MaxJavaStackTraceDepth=1024,可以将这个参数设置为-1,那将会全部输出对应的堆栈

如果进程消失了,但是留下了crash日志,那请检查下crash日志里的Current thread的stack范围,以及RSP寄存器的值,如果RSP寄存器的值是超出这个stack范围的,那说明是栈溢出了。

如果crash日志也没有留下,那只能通过coredump来分析了,在进程运行前,先执行ulimit -c unlimited,然后再跑进程,在进程挂掉之后,会产生一个core.的文件,然后再通过jstack $JAVA_HOME/bin/java core.来看输出的栈,如果正常输出了,那就可以看是否存在很长的调用栈的线程,当然还有可能没有正常输出的,因为jstack的这条从core文件抓栈的命令其实是基于serviceability agent来实现的,而SA在某些版本里是存在bug的,当然现在的SA也不能说完全没有bug,还是存在不少bug的,祝你好运。

如何解决栈溢出的问题

这个需要具体问题具体分析,因为导致栈溢出的原因很多,提三个主要的:

java代码写得不当,比如出现递归死循环,这也是最常见的,只能靠写代码的人稍微小心了

native代码有栈上分配的逻辑,并且要求的内存还不小

线程栈空间设置比较小

有时候我们的代码需要调用到native里去,最常见的一种情况譬如java.net.SocketInputStream.read0方法,这是一个native方法,在进入到这个方法里之后,它首先就要求到栈上去分配一个64KB的缓存(64位linux),试想一下如果执行到read0这个方法的时候,剩余的栈空间已经不足以分配64KB的内存了会怎样?也许就是一开头我们提到的crash,这只是一个例子,还有其他的一些native实现,包括我们自己也可能写这种native代码,如果真有这种情况,我们就需要好好斟酌下我们的线程栈到底要设置多大了。

如果我们的代码确实存在正常的很深的递归调用的话,通常是我们的栈可能设置太小,我们可以通过-Xss或者-XX:ThreadStackSize来设置java线程栈的大小,如果两个参数都设置了,那具体有效的是写在后面的那个生效。顺便提下,线程栈内存是和java heap独立的内存,并不是在java heap内分配的,是直接malloc分配的内存。

线程栈大小

在jvm里,线程其实不仅仅只有一种,比如我们java里创建的叫做java线程,还有gc线程,编译线程等,默认情况下他们的栈大小如下:

size_t os::Linux::default_stack_size(os::ThreadType thr_type) {

// default stack size (compiler thread needs larger stack)

#ifdef AMD64

size_t s = (thr_type == os::compiler_thread ? 4 * M : 1 * M);

#else

size_t s = (thr_type == os::compiler_thread ? 2 * M : 512 * K);

#endif // AMD64

return s;

}复制

可见默认情况下编译线程需要的栈空间是其他种类线程的4倍。

各种类型的线程他们所需要的栈的大小其实是可以通过不同的参数来控制的:

switch (thr_type) {

case os::java_thread:

// Java threads use ThreadStackSize which default value can be

// changed with the flag -Xss

assert (JavaThread::stack_size_at_create() > 0, "this should be set");

stack_size = JavaThread::stack_size_at_create();

break;

case os::compiler_thread:

if (CompilerThreadStackSize > 0) {

stack_size = (size_t)(CompilerThreadStackSize * K);

break;

} // else fall through:

// use VMThreadStackSize if CompilerThreadStackSize is not defined

case os::vm_thread:

case os::pgc_thread:

case os::cgc_thread:

case os::watcher_thread:

if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);

break;

}复制

java_thread的stack_size,其实就是-Xss或者-XX:ThreadStackSize的值

compiler_thread的stack_size,是-XX:CompilerThreadStackSize指定的值

vm内部的线程比如gc线程等可以通过-XX:VMThreadStackSize来设置

JVM 里栈溢出的实现

JVM 里的栈溢出到底是怎么实现的,得从栈的大致结构说起:

// Java thread:

//

// Low memory addresses

// +------------------------+

// | |\ JavaThread created by VM does not have glibc

// | glibc guard page | - guard, attached Java thread usually has

// | |/ 1 page glibc guard.

// P1 +------------------------+ Thread::stack_base() - Thread::stack_size()

// | |\

// | HotSpot Guard Pages | - red and yellow pages

// | |/

// +------------------------+ JavaThread::stack_yellow_zone_base()

// | |\

// | Normal Stack | -

// | |/

// P2 +------------------------+ Thread::stack_base()

//

// Non-Java thread:

//

// Low memory addresses

// +------------------------+

// | |\

// | glibc guard page | - usually 1 page

// | |/

// P1 +------------------------+ Thread::stack_base() - Thread::stack_size()

// | |\

// | Normal Stack | -

// | |/

// P2 +------------------------+ Thread::stack_base()

//

// ** P1 (aka bottom) and size ( P2 = P1 - size) are the address and stack size returned from

// pthread_attr_getstack()复制

linux下java线程栈是从高地址往低地址方向走的,在栈尾(低地址)会预留两块受保护的内存区域,分别叫做yellow page和red page,其中yellow page在前,另外如果是java创建的线程,最后并没有图示的一个page的glibc guard page,非java线程是有的,但是没有yellow和red page,比如我们的gc线程,注意编译线程其实是java线程。

除了yellow page和red page,其实还有个shadow page,这三个page可以分别通过vm参数-XX:StackYellowPages,-XX:StackRedPages,-XX:StackShadowPages来控制。当我们要调用某个java方法的时候,它需要多大的栈其实是预先知道的,javac里就计算好了,但是如果调用的是native方法,那这就不好办了,在native方法里到底需要多大内存,这个无法得知,因此shadow page就是用来做一个大致的预测,看需要多大的栈空间,如果预测到新的RSP的值超过了yellowpage的位置,那就直接抛出栈溢出的异常,否则就去新的方法里处理,当我们的代码访问到yellow page或者red page里的地址的时候,因为这块内存是受保护的,所以会产生SIGSEGV的信号,此时会交给JVM里的信号处理函数来处理,针对yellow page以及red page会有不同的处理策略,其中yellow page的处理是会抛出StackOverflowError的异常,进程不会挂掉,也就是文章开头提到的第一个场景,但是如果是red page,那将直接导致进程退出,不过还是会产生Crash的日志,也就是文章开头提到的第二个场景,另外还有第三个场景,其实是没有栈空间了并且访问了超过了red page的地址,这个时候因为栈空间不够了,所以信号处理函数都进不去,因此就直接crash了,crash日志也不会产生。

if (sig == SIGSEGV) {

address addr = (address) info->si_addr;

// check if fault address is within thread stack

if (addr < thread->stack_base() &&

addr >= thread->stack_base() - thread->stack_size()) {

// stack overflow

if (thread->in_stack_yellow_zone(addr)) {

thread->disable_stack_yellow_zone();

if (thread->thread_state() == _thread_in_Java) {

// Throw a stack overflow exception. Guard pages will be reenabled

// while unwinding the stack.

stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::STACK_OVERFLOW);

} else {

// Thread was in the vm or native code. Return and try to finish.

return 1;

}

} else if (thread->in_stack_red_zone(addr)) {

// Fatal red zone violation. Disable the guard pages and fall through

// to handle_unexpected_exception way down below.

thread->disable_stack_red_zone();

tty->print_raw_cr("An irrecoverable stack overflow has occurred.");

// This is a likely cause, but hard to verify. Let's just print

// it as a hint.

tty->print_raw_cr("Please check if any of your loaded .so files has "

"enabled executable stack (see man page execstack(8))");

} else {

// Accessing stack address below sp may cause SEGV if current

// thread has MAP_GROWSDOWN stack. This should only happen when

// current thread was created by user code with MAP_GROWSDOWN flag

// and then attached to VM. See notes in os_linux.cpp.

if (thread->osthread()->expanding_stack() == 0) {

thread->osthread()->set_expanding_stack();

if (os::Linux::manually_expand_stack(thread, addr)) {

thread->osthread()->clear_expanding_stack();

return 1;

}

thread->osthread()->clear_expanding_stack();

} else {

fatal("recursive segv. expanding stack.");

}

}

}

}

......

if (stub != NULL) {

// save all thread context in case we need to restore it

if (thread != NULL) thread->set_saved_exception_pc(pc);

uc->uc_mcontext.gregs[REG_PC] = (greg_t)stub;

return true;

}

// signal-chaining

if (os::Linux::chained_handler(sig, info, ucVoid)) {

return true;

}

if (!abort_if_unrecognized) {

// caller wants another chance, so give it to him

return false;

}

if (pc == NULL && uc != NULL) {

pc = os::Linux::ucontext_get_pc(uc);

}

// unmask current signal

sigset_t newset;

sigemptyset(&newset);

sigaddset(&newset, sig);

sigprocmask(SIG_UNBLOCK, &newset, NULL);

VMError err(t, sig, pc, info, ucVoid);

err.report_and_die();

ShouldNotReachHere();复制

了解上面的场景之后,再回过头来想想JVM为什么要设置这几个page,其实是为了安全,能预测到栈溢出的话就抛出StackOverfolwError,而避免导致进程挂掉。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java栈溢出现象_JVM源码分析之栈溢出完全解读 的相关文章

  • layout注意事项_注意事项

    layout注意事项 In the post we will look at how you can orchestrate real time customer journeys with Adobe s Journey Orchestr
  • 【华为OD统一考试B卷

    华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一修改为OD统一考试 A卷 和OD统一考试 B卷 你收到的链接上面会标注A卷还是B卷 请注意 根据反馈 目前大部分收到的都是
  • from: can't read /var/mail/xxx 解决方法

    在执行一个发包脚本的时候 遇到了如下问题 from can t read var mail scapy all 原因 脚本是没有问题的 但它并不是可以被python执行的可执行文件 解决方法 1 使用python 脚本名字 命令执行脚本 2
  • k210自学快速入门(附网址)

    一 获得下载工具IDE https dl sipeed com MAIX MaixPy ide 安装和配置见该博客 http t csdn cn 464lj 二 下载固件 1 用于程序的执行下载站 Sipeed 注意 minimum是最小版
  • sqlalchemy create_engine关于连接池的几个参数

    pool size 设置连接池中 保持的连接数 初始化时 并不产生连接 只有慢慢需要连接时 才会产生连接 例如我们的连接数设置成pool size 10 如果我们的并发量一直最高是5 那么我们的连接池里的连接数也就是5 当我们有一次并发量达
  • 级联菜单的动态实现(数据库绑定)

    效果预览 数据库设计 第一类型 第二类型 第三类型 Controller层代码 RequestMapping noFilterGetAllInfo public void noFilterGetAllInfo Integer firstSe
  • 【调试经验】MySQL - fatal error: mysql/mysql.h: 没有那个文件或目录

    机器环境 Ubuntu 22 04 3 LTS 报错问题 在编译一个项目时出现了一段SQL报错 CGImysql sql connection pool cpp 1 10 fatal error mysql mysql h 没有那个文件或目
  • python推荐系统学习笔记(5)——基于图的模型推荐算法

    python推荐系统学习笔记 5 基于图的模型推荐算法 2 1 用户行为数据的二分图表示 为可以把基于邻域的模型看作基于图的模型的简单形式 用户物品二分图模型 对于数据集中每一个二元组 u i 图中都有一套对应的边e vu vi 其中vu属
  • java listnode 合并链表_java实现链表合并

    输入两个单调递增的链表 输出两个链表合成后的链表 当然我们需要合成后的链表满足单调不减规则 最容易想到的就是新建一个链表 一个一个将节点连接到新链表中 代码 public ListNode Merge ListNode list1 List
  • Linux C/C++解析xls

    libxls作为开源库 支持在Linux C C 环境下解析 读 xls文件 github提供了源码 https github com libxls libxls 但是github的源码需要一堆辅助工具 才能够编译出libxls的C静态库
  • C++中while循环中cin语句被跳过问题解析

    今天在写代码的时候 遇到了一个非常奇怪的问题 while true int select cout lt lt 请输入查找的方式 lt lt endl cout lt lt 1 按职工编号查找 lt lt endl cout lt lt 2
  • 学习数据数据结构的意义

    来源 我是码农 转载请保留出处和链接 本文链接 http www 54manong com id 18 什么是数据结构 为什么要学习数据结构 数据结构是否是一门纯数学课程 它在专业课程体系中起什么样的作用 我们要怎么才能学好数据结构 相信同
  • TCP建立连接三次握手和释放连接四次握手

    TCP建立连接三次握手和释放连接四次握手 尊重原创 转载请注明出处 http blog csdn net guyuealian article details 52535294 在谈及TCP建立连接和释放连接过程 先来简单认识一下TCP报文
  • 哪里期货开户低手续费高交返

    国内商品期货开户流程和股票开户的流程很像似 没有开过期货户的 想要开期货户只需要知道这个就行了 可以考虑传统方式去营业部线下开户 但是线下开户弊端手续费较高 地区性垄断 前些年网上开户还没普及时 基本都采用线下开户的方式 随着互联网时代的到
  • QTextCodec中的setCodecForTr等终于消失了 (Qt5)

    在Qt4中 国内很多新手都喜欢 不分青红皂白地使用如下3行代码 QTextCodec setCodecForTr QTextCodec setCodecForCStrings QTextCodec setCodecForLocale 尽管之
  • C++设计模式——单例模式

    我们应该知道 C 中有21种设计模式 常见的有单例模式 迭代器模式 工厂模式 抽象工厂模式 观察者模式 今天我们先来说一下单例模式 单例模式 Singleton 是设计模式中最为简单 最为常见 最容易实现的模式 单例模式就是怎样去创建一个唯
  • 基于Simulink的BPSK调制通信系统建模和仿真

    基于Simulink的BPSK调制通信系统建模和仿真 本文将介绍如何使用Matlab的Simulink工具进行BPSK调制通信系统的建模和仿真 BPSK Binary Phase Shift Keying 是一种常用的数字调制技术 适用于低
  • python的ndarray、series和dataframe类型转化

    文章目录 创建ndarray类型数据 使用list创建series series和ndarray转化 series转换为ndarray ndarray转换为series 使用list创建dataframe pd DataFrame 将nda
  • 因果推断-【The MineThatData E-Mail Analytics And Data Mining Challenge】思路分析与Python实现代码

    目录 一 数据集介绍 二 问题及分析思路 1 问题 2 分析思路 三 代码 一 数据集介绍 数据集来源于用户在网上的购物行为 涵盖了过去一年有购买行为的64000个用户 这些用户被用于电子邮件营销活动的实验分析 实验的目的是衡量哪个版本的电

随机推荐

  • 责任中心(成本中心、利润中心、收入中心、费用中心和投资中心)

    转帖自智库百科 http wiki mbalib com 责任中心 出自MBA智库百科 http wiki mbalib com 责任中心 Responsibility Center 目录 隐藏 1 什么是责任中心 2 责任中心的特征 3
  • QT 第四天

    一 设置一个闹钟 pro QT core gui texttospeech greaterThan QT MAJOR VERSION 4 QT widgets CONFIG c 11 The following define makes y
  • OSPF路由协议(二)

    作者介绍 作者 小刘在C站 每天分享课堂笔记 一起努力 共赴美好人生 夕阳下 是最美的绽放 目录 一 Router id 二 DR BDR 三 DR BDR 选举过程 四 ospf 度量值 cost 代价
  • YOLOv5模型改进策略源码示例

    YOLOv5模型改进策略源码示例 YOLO目标检测算法作为单阶段目标检测算法的代表在各个领域都有广泛的应用 在前几篇文章中我们已经对YOLO的Backbone Neck Head进行了较为详细的解读 这篇文章主要是从添加注意力机制来提升YO
  • Window安装Node.js npm appium Appium Desktop

    Window安装Node js npm appium appium Desktop 1 安装nodejs 参考链接 https blog csdn net weixin 42064877 article details 131610918
  • Linux 线程同步

    文章目录 一 线程同步介绍 同步与互斥概述 线程同步问题 二 互斥锁 为什么需要互斥锁 互斥锁 Mutex 介绍 互斥锁相关 API 死锁 DeadLock 三 读写锁 读写锁概述 读写锁相关 API 四 生产者与消费者模型 五 条件变量
  • linux最佳线程数

    最佳线程数 性能压测的情况下 起初随着用户数的增加 QPS会上升 当到了一定的阀值之后 用户数量增加QPS并不会增加 或者增加不明显 同时请求的响应时间却大幅增加 这个阀值我们认为是最佳线程数 为什么要找最佳线程数 1 过多的线程只会造成
  • 算法设计与分析: 6-3 最小权顶点覆盖问题

    6 3 最小权顶点覆盖问题 问题描述 给定一个赋权无向图 G V E 每个顶点 v V 都有一个权值 w v 如果 U V U V U subseteq V 且对任意 u v E 有 u U 或 v U 就称 U 为图 G 的一个顶点覆盖
  • Pycharm程序调试(Debug+断点)

    主要思路 利用断点 注 打断点之后 程序运行到断点的哪一行处 但此行并未执行 调试的过程分为三步 第一步 在你想要调试的地方 打上断点 第二步 使用调试模式来运行这个 python 程序 第三步 使用各种手段开始代码调试 一 图文教程 1
  • Tomcat架构解析以及设计借鉴

    Tomcat 发展这么多年 已经比较成熟稳定 在如今 追新求快 的时代 Tomcat 作为 Java Web 开发必备的工具似乎变成了 熟悉的陌生人 难道说如今就没有必要深入学习它了么 学习它我们又有什么收获呢 静下心来 细细品味经典的开源
  • 开源软件大集合

    http a note sourceforge net A Note 4 2 1 可在Windows桌面放置便笺 并可提供闹钟提醒功能 http www xs4all nl edienskeAbakt 0 9 能够以压缩方式对文档进行备份
  • llama.cpp LLM模型 windows cpu安装部署;运行LLaMA2模型测试

    参考 https www listera top ji xu zhe teng xia chinese llama alpaca https blog csdn net qq 38238956 article details 1301135
  • 设置linux-kali 2022语言为中文(保姆级图文)

    目录 友情提示 1 打开终端 2 打开设置 3 修改设置 4 重启生效设置 总结 欢迎关注 网络工程专业 系列 持续更新中 欢迎关注 网络工程专业 系列 持续更新中 在安装完 kali linux2022 时 操作系统默认语言为英文 初学者
  • 【论文阅读-TPAMI2021】Curriculum Learning(课程学习)综述

    简介 Curriculum learning CL 课程学习 是一种模型训练策略 通过先让模型学习简单数据后再学习困难数据的方式模拟学生进行课程学习的场景 通用的课程学习框架为Difficulty Measurer 困难程度评估 Train
  • 不懂Python装饰器,你敢说会Python?

    对于Python学习者 一旦过了入门阶段 你几乎一定会用到Python的装饰器 它经常使用在很多地方 比如Web开发 日志处理 性能搜集 权限控制等 还有一个极其重要的地方 那就是面试的时候 对 装饰器是面试中最常见的问题之一 实战入门 抛
  • webpack从入门到放弃(二:基本属性)

    本节介绍webpack五大核心概念 一 entry 入口 指示 Webpack 从哪个文件开始打包 webpack是根据依赖关系进行打包 以入口文件为起点 根据依赖关系形成依赖树 在生产模式打包时 根据tree shaking未引用的文件不
  • 8259初始化命令字(ICW1-ICW4)

    8259A的中断操作功能很强 包括中断的请求 屏蔽 排队 结束 级联以及提供中断类型号和查询等操作 并且其操作的方式又有不同 它既能实现向量中断 又能进行中断查询 它可以用于16位机 也可用于8位机 因此 使用起来感到复杂且不好掌握 为此
  • idea 提交远程库冲突解决

    idea 提交远程库冲突解决 github团队协作 正常开发 管理得好的话 不会出现代码冲突问题 项目经理会划分模块 每个小组成员各自开发模块 公共的代码由专门的人负责维护 但是偶尔管理沟通问题导致出现冲突偶尔也是会出现的 冲突出现场景过程
  • Visual Studio Code中英文的切换

    我在学习 Flutter 的时候 使用过 VsCode 来开发 一般来说 安装好的 VsCode 都是英文版的 有些人可能不太习惯用英文版的 不过没有关系 我在这里提供中英文切换的方法给大家 切换为中文 1 点击 1 中的选项 在 2 中的
  • java栈溢出现象_JVM源码分析之栈溢出完全解读

    概述 之所以想写这篇文章 其实是因为最近有不少系统出现了栈溢出导致进程crash的问题 并且很隐蔽 根本原因还得借助coredump才能分析出来 于是想从JVM实现的角度来全面分析下栈溢出的这类问题 或许你碰到过如下的场景 日志里出现了St