当我们递归调用Java方法时,很可能会出现StackOverflowError,我们会认为此时栈内存溢出了,那么这个栈内存溢出虚拟机是如何检测的呢?
是累加分配的内存与栈大小进行比较,还是有更好的方式呢?
更多问答 >>
-
每日一问 | targetSdkVersion 有什么神奇的魔力?
2023-05-24 17:30 -
每日一问 | Android 模块化依赖中的资源冲突该如何规避?
2023-05-24 17:30 -
每日一问 | Android 默认开启硬件加速与设置hardwareAccelerated是一回事吗?
2023-05-24 17:30 -
每日一问 | 瘦身属性?对android:extractNativeLibs属性的探讨
2022-12-07 14:43 -
每日一问 | App在运行状态,可以动态安装apk,并且不重新启动吗?
2022-11-20 12:46
洋神,你真的好久没搞每日一问了啊……
本回答介绍的是 Android 上 ART 的处理方式。想要回答这个问题,首先我们需要知道几个预备知识:
首先可以来看看解释执行的情况,随便点开一个函数:
http://aospxref.com/android-13.0.0_r3/xref/art/runtime/interpreter/interpreter.cc?fi=EnterInterpreterFromInvoke#EnterInterpreterFromInvoke第一眼就能看见一个 if,如果这个 if 的条件成立就会抛出 StackOverflowError。__builtin_frame_address 是 gcc 内置的获取方法调用信息的函数,这里获取的是“当前函数的栈帧地址”,如果小于某个值就会认为发生栈溢出,抛出 StackOverflowError。为什么是小于?因为栈是向下增长的。接着看看函数被编译后是怎么检查的。用
cmd package compile -m everything -f 包名
这条命令强制编译,然后把 oat 拿出来,用 oatdump 反汇编看看。随便点开一个函数(因为我刚好在一台 arm 机子上用了 oatdump ,直接复制过来了,所以这里是 Thumb-2 指令集):随便看几个函数,你会发现基本上每个函数的开头都有这几条固定指令。为什么?
这两条指令里,第一条把 sp 寄存器也就是所谓的栈帧指针减去 8192,赋值给了 r12 寄存器;第二条读取了从 r12 寄存器指向的内存的值,赋值给 r12 寄存器。由于栈向下增长,这里 sp - 8192 指向的内存应该没有被实际分配所以无意义,检查后续指令也会发现这个读取到的值根本没有用到,为什么要多此一举呢?其实这里就是在检查栈有没有溢出。栈是一段连续的内存空间,提前使用 mmap 分配,当栈(快要)溢出的时候,sp - 8192 这个内存地址就会超出这段提前分配的内存空间,接下来尝试访问这个地址指向的内存就会触发段错误,从而导致程序中断执行跳转到 art 提前设置好的 signal handler 中。art 的 signal handler 会检测出这种情况,并抛出 StackOverflowError。http://aospxref.com/android-13.0.0_r3/xref/art/runtime/arch/arm64/fault_handler_arm64.cc#142