概述

之所以想写这篇文章,其实是因为最近有不少系统出现了栈溢出导致进程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.<pid>的文件,然后再通过jstack $JAVA_HOME/bin/java core.<pid>来看输出的栈,如果正常输出了,那就可以看是否存在很长的调用栈的线程,当然还有可能没有正常输出的,因为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线程,编译线程等,默认情况下他们的栈大小如下:

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

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

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

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

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

JVM里栈溢出的实现

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

会预留两块受保护的内存区域,分别叫做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日志也不会产生。

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

JVM源码分析之栈溢出完全解读的更多相关文章

  1. JVM源码分析之SystemGC完全解读

    JVM源码分析之SystemGC完全解读 概述 JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可 ...

  2. JVM源码分析之FinalReference完全解读

    Java对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了4种其他引用:SoftReference.WeakReference.PhantomReference.FinalRefe ...

  3. JVM源码分析之堆外内存完全解读

    JVM源码分析之堆外内存完全解读   寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...

  4. JVM源码分析之一个Java进程究竟能创建多少线程

    JVM源码分析之一个Java进程究竟能创建多少线程 原创: 寒泉子 你假笨 2016-12-06 概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于L ...

  5. JVM源码分析之Metaspace解密

        概述 metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm,这块空间很有自己的特点,前段时间公司这块的问题太多了,主要是因为升级了中间件所 ...

  6. JVM源码分析-JVM源码编译与调试

    要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ...

  7. JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)

    概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...

  8. JVM源码分析-类加载场景实例分析

    A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?. 场景如下: public cl ...

  9. JVM源码分析之堆内存的初始化

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十五篇. ​ 今天呢!灯塔君跟大家讲: JVM源码分析之堆内存的初始化   堆初始化 Java堆的初始化入口位于Univ ...

随机推荐

  1. 谷歌Chrome浏览器无法安装插件的解决方法

    Chrome浏览器已替代了个人多年使用的遨游浏览器,但众所周知,国内的环境无法正常登录谷歌账户.无法访问应用商店,而Chrome主版本号大于66的只能从Chrome应用商店下载并安装插件,这不是死结吗 ...

  2. android ndk-build 编译静态库libxx.a 以及Android studio openssl 静态库配置(cmake)

    android ndk-build 编译静态库libxx.a 需求场景: 目前有安卓编码好的现在的openssl的两个.a,我们需要调用openssl的函数,并把功能再封装成.a; 这样使用时,在an ...

  3. JFinal提示:java.lang.RuntimeException: dao 只允许调用查询方法

    public class UserModel extends Model<UserModel>{ public static final UserModel userDao = new U ...

  4. ORA-19602: cannot backup or copy active file in NOARCHIVELOG mode

    备份数据库,报错如下 RMAN> backup database; Starting backup at -JAN- allocated channel: ORA_DISK_1 channel ...

  5. Ubuntu18.04的网络管理netplan和防火墙ufw

    Netplan Ubuntu18.04使用的网络管理是netplan, 配置文件在/etc/netplan/下 刚安装完成的配置是这样的 刚安装完成的配置是这样的 network: ethernets ...

  6. linux下查看端口占用

    1. lsof -i:端口号 用于查看某一端口的占用情况 需要注意的是,centos默认是没有安装lsof(list open files)的,需要手动安装 yum install lsof 各列代表 ...

  7. 在Asp.Net MVC中使用Repeater控件

    使用Repeater控件在视图中展示图表信息,Repeater控件的使用概述: <asp:Repeater ID="Repeater1" runat="server ...

  8. JS控制音频顺序播放

    做一项目,用到“叫号功能”,网页上有一“叫号”按钮,点击后就读数据库中存的号码,如123号, 然后就发声音出来, 思路是网上下载0123456789的叫号声音,然后按钮点击事件里就在JS里写用那个HT ...

  9. MongoDB T-shirt 来了

    嘚瑟一下.                    

  10. Hack mind

    试 卷 姓名________ 学号________ 首先我们来看这个,试卷的名字和编号填写,这个部分有“漏洞”吗?有 学生的姓名和编号都写在这儿,没有做任何保护措施,因此,你只要偷看了某人的试卷上的这 ...