JVM源码分析之栈溢出完全解读
概述
之所以想写这篇文章,其实是因为最近有不少系统出现了栈溢出导致进程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源码分析之栈溢出完全解读的更多相关文章
- JVM源码分析之SystemGC完全解读
JVM源码分析之SystemGC完全解读 概述 JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可 ...
- JVM源码分析之FinalReference完全解读
Java对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了4种其他引用:SoftReference.WeakReference.PhantomReference.FinalRefe ...
- JVM源码分析之堆外内存完全解读
JVM源码分析之堆外内存完全解读 寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...
- JVM源码分析之一个Java进程究竟能创建多少线程
JVM源码分析之一个Java进程究竟能创建多少线程 原创: 寒泉子 你假笨 2016-12-06 概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于L ...
- JVM源码分析之Metaspace解密
概述 metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm,这块空间很有自己的特点,前段时间公司这块的问题太多了,主要是因为升级了中间件所 ...
- JVM源码分析-JVM源码编译与调试
要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ...
- JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)
概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...
- JVM源码分析-类加载场景实例分析
A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?. 场景如下: public cl ...
- JVM源码分析之堆内存的初始化
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十五篇. 今天呢!灯塔君跟大家讲: JVM源码分析之堆内存的初始化 堆初始化 Java堆的初始化入口位于Univ ...
随机推荐
- Idea调试
Idea调试 学习了:https://www.jb51.net/article/128965.htm 1,多线程同时断点: 2,drop frame 回退调试: 3,条件断点/片段代码: 4,调试的时 ...
- Chrome网页性能分析工具
performance-analyser https://chrome.google.com/webstore/detail/performance-analyser/djgfmlohefpomchf ...
- KVM虚拟化管理 virt manager常用操作
部分涉及到Linux.Nginx.tomcat.MySQL等的点滴操作记录,时间长了,就忘掉了,偶尔整理一下操作的history,就此简要备份一下: [原][BG]-一次虚拟化环境实践简要记录: ht ...
- windows server 2012 浏览器IE10无法下载。
cannot download in IE 10 of window server 2012 中文版解决办法: 1.打开IE,按F12,选择兼容浏览器为IE 9 2.选择IE的Internet选项菜单 ...
- Spark机器学习(4):朴素贝叶斯算法
1. 贝叶斯定理 条件概率公式: 这个公式非常简单,就是计算在B发生的情况下,A发生的概率.但是很多时候,我们很容易知道P(A|B),需要计算的是P(B|A),这时就要用到贝叶斯定理: 2. 朴素贝叶 ...
- windows 系统中的 afd 驱动
afd 的全称是 Ancillary Function Driver for WinSock,是 windows 系统网络部分的核心工具.同 Linux 类似,windows 的 socket 最终也 ...
- JMX-Java Management Extensions
JMX全称Java Management Extensions, 为Java应用提供管理扩展功能.在Java 5的时候引入 概念 Name Description MBean 全称为Managed B ...
- 学习一个Vue模板项目
最开始学习Vue的时候,不建议直接使用模板,而应该自己从头写起.模板都是人写的,要坚信"人能我能".只有自己亲自实践,才能促进自己主动思考,才能对模板.框架有深刻的理解. 在Git ...
- Starting httpd: httpd: Could not reliably determine the server's fully qualified domain name
启动apache的时候,报告以下消息提示: Starting httpd: httpd: Could not reliably determine the server's fully qualifi ...
- 收藏清单: python测试数据生成及代码扫描最全工具列表
Test Data manipulation 测试数据的操作和处理 faker - 生成假数据的python库 fake2db - 创建假数据库 ForgeryPy - 使用起来很简单的假数据生成库. ...