昨天JAVA圈 , 美团曝出了一道变态级面试题:为什么栈溢出后线程没有崩溃?为什么这段代码会永远执行下去?
文章插图
我的几个交流群、VIP群 , 争论不休 , 看大家都是在Java层找答案 。很明显 , 这个问题的答案不在Java层 , 接下来咱们分析下这个问题 , 然后一起去找答案 , 争取下次被问到 , 一举击溃面试官的心理防线:偶滴乖乖 , 是这道题难度太小还是我太菜?
我会按照看到这个问题我是如何分析如何做实验如何得出结论 , 层层递进展开 , 你读起来应该会越来越嗨皮!瓦特?你没嗨皮?你是不是没看懂哦?
01我的第一反应
我的第一反应是catch Error会永远执行 , 那catch Exception呢?接下来上代码
文章插图
看运行结果
文章插图
会报栈OOM
我脑海中马上想到两个问题:
1、这个栈深度是多少时抛出的?
2、为什么catch Exception会抛出?
怎么查看栈深度呢?JVM提供了相关方法吗?木有!我们通过linux命令来统计
文章插图
报栈OOM时 , 栈的深度是1024 , 这个数字大家记一下 , 后面还会讲到 , 很关键!
接下来第二个问题 , 为什么catch Exception会抛出OOM?其实对于这段代码 , 这个问题就是坑 , 因为栈溢出抛出的是StackOverflowError , 你catch Exception是无效的 , 等同于
文章插图
什么?你不信?你把代码改成这样 , 看看catch代码块会不会执行
文章插图
02第二段探索
研究完了我的第一反应并得到答案以后 , 我就开始了我的第二段探索:这个Java程序能够无限执行 , 这个能力是操作系统自带的还是JVM自己开发出来的?
我们来看看操作系统是否具备让程序永远运行的能力 。怎么测试呢?需要你懂一点Linux多线程相关的东西 。不懂也没关系 , 看我演示的现象及结论即可 。底层内功重不重要 , 从这里可见一斑 。
文章插图
可以看到 , Linux系统默认是不支持程序无限执行的
为什么最后会报段错误呢?因为Linux系统创建线程 , 默认的栈大小是8M , 程序无限递归把8M用光了 , 但是程序还不会终止 , 不自觉会用到8M之外的内存
这里面还有个隐含知识点 , 栈图 , 没有这个基础你可能很难理解上面讲的 , 放个图帮助你理解
文章插图
03第三段探索
既然Linux系统没有提供这样的能力 , 然JVM能如此 , 大胆猜想可能的原因:
1、JVM改变了系统默认栈大小8M , 可能改成了很大很大 。如果是这样 , 那我们看这段程序的无限执行其实是假象 , 如果让它一直跑 , 跑很久很久 , 可能它就over了 。
2、JVM内部做了优化 , 比如栈帧回溯、递归内联、尾递归优化 。验证这个的时候还得考虑方法执行的两个阶段:解释执行 , 执行JIT及时编译后的代码 。是真滴复杂!
我们开始看下JVM的主线程有没有改线程栈的默认大小 , 改成了多少 。这个要怎么看?单步调试openjdk
文章插图
JVM把改成了1M 。
JVM的虚拟机栈是在操作系统的线程栈上进行拓展的 , Linux系统的默认线程栈是8M , 如果是递归调用 , 一下就跑完了 。但是美团的这段程序 , 很明显跑了很久都没结束 , 所以这种情况pass , 只剩最后一种情况 。
04第四段探索
前面提到了两个关键的东西:栈深度1024 , 如果程序递归调用把线程栈用光了会报段错误 。这两个玩意这里都要用上 。开始探索
推荐阅读
- 美国网络攻击他国的程序,被中国研究员破解!45个国家遭黑客攻击
- 一点隐私都没有了?聊天记录打码也不安全:大神亲自下场破解
- 新版WinRAR总是弹出广告怎么破?无需破解,6步解决
- Win11最低硬件要求怎么破解,Win11最低硬件要求破解的解决方法
- 彻底搞懂字符编码
- 一起SSH暴力破解
- APP渗透技巧 | 逆向app破解数据包sign值,实现任意数据重放添加
- Python生成遍历暴力破解密码,实战的效果差强人意了
- Python黑科技:暴力破解,你的密码是否安全呢?
- 用python暴力破解有密码的zip文件